The View and Controller components for tables in the MQL5 MVC paradigm: Containers
Contents
- Introduction
- Singleton Class as a Shared Data Manager
- Classes for Organizing Auto-Repeat of Button Clicks
- Refining Base Classes
- Object List Class
- Base Class of a Graphic Element
- Refining Simple Controls
- Container Classes for Placing Controls
- Testing the Result
- Conclusion
Introduction
In modern user interfaces, it is quite often necessary to compactly and conveniently display large amounts of various data. For these purposes, special controls called containers with support for scrolling content are used. This approach allows for placing tables and other graphical elements in a limited window space, providing the user with quick and intuitive access to information.
As part of the development of TableView control in MVC (Model-View-Controller) paradigm, we have already created Model component - a table model and started creating the View and Controller components. In the last article, simple but quite functional controls were created. Complex controls will be assembled from such elements. Today we will write such control classes as Panel, GroupBox and Container — all three elements are containers for placing various controls on them.
- The Panel control is a panel that enables to place any number of other controls on it. When moving the panel to new coordinates, all the controls located on it also move along with the panel. Thus, the panel is a container for the controls located on it. However, this element does not have scrollbars that allow for scrolling through container contents if it goes beyond the panel boundaries. Such content is simply clipped to the container boundaries.
- GroupBox control is a set of elements organized into one group. It is inherited from the panel and enables to group up elements according to some common purpose, for example, a group of RadioButton elements, where only one element from the entire group can be selected, and the rest of the group elements are deselected.
- The Container control. Allows attaching only one control to yourself. If the attached element extends beyond the container, scrollbars appear at the container. They enable to scroll through the contents of the container. To place any number of controls in a container, it is necessary to place a panel in it, and attach the required number of controls to the panel. Thus, the container will scroll through the panel, and the latter will shift its contents after scrolling.
Thus, in addition to the three specified main controls, we have to create classes for creating scrollbars — the thumb class (Thumb) and the scrollbar class (ScrollBar). There will be two such classes — for vertical and horizontal scrollbars.
If you look closely at the operation of scroll buttons located at the edges of scroll bars, notice that when holding the button down for a long time, automatic scrolling turns on. I.e., the button automatically starts sending click events. For this behavior, we will create two more auxiliary classes — the delay counter class and the actual event auto-repeat class.
The delay counter class can be used to organize waiting without freezing the program run, and the event auto-repeat class will be implemented so that we can specify for it which event it should send. This will make it possible to use it not only to organize an auto-repeat of button clicks, but also for any other algorithms that require repeating events after a certain time with a certain frequency.
At the final stage, tables will be placed inside a universal container, which will provide scrolling using scrollbars. Such a container will become the basis for building complex and flexible interfaces, allowing not only to work with tables, but also to use it in other components, for example, when creating a multi—page notepad or other user elements for MetaTrader 5 client terminal.
It is worth noting how the controls work. Each control element is equipped with event-based functionality (Controller component) and reacts appropriately to interaction with the mouse cursor.
With certain actions, the element that is being interacted with sends an event to the chart. That event should be received and handled by another control. But all the elements receive such events. It is necessary to determine which element is active (over which the cursor is currently located), and process only the messages coming from that element. That is, when you hover the mouse cursor over a control, it should be marked as active, while the rest — inactive elements — should not be handled.
To organize selection of an active element, it is necessary to make sure that each control has access to such information. There are different ways to do this. For example, you can create a list in which names of all the elements created will be registered, search for a match in it for the name of the object that the cursor is currently on, and work with the found object in this list.
This approach is possible, but it results in complicating the code and working with it. It's easier to make a single object that is globally accessible in the program, and record the name of the active control into it. The remaining elements will immediately see the name of the active element and decide whether to handle incoming messages or not, without additional search in any database.
A singleton class can be such a public class:
A singleton class is a design pattern that guarantees the existence of only one instance of a given class during the program lifetime and provides a global access point to that instance.
The purpose of singleton
Singleton is used when it is necessary that some object has only one instance, and this instance is accessible from any part of the program. Examples: settings manager, logger, database connection pool, resource manager, etc.
How singleton works
- A hidden constructor: The class constructor is declared private or protected to prevent the creation of instances from the outside.
- Static variable: A static variable is created inside the class, storing a single instance of the class.
- Static access method: To get an instance of a class, a static method is used (for example, Instance() or getInstance() ), which creates an object on the first access and returns it on subsequent calls.
Singleton is a class that can only be created once, and that single instance is globally accessible. This is useful for managing shared resources or application status.
Let’s implement such a class.
Singleton Class as a Shared Data Manager
In the last article, all the library codes were located at \MQL5\Indicators\Tables\Controls\. Here we are interested in both files: Base.mqh and Control.mqh. We will refine them today.
Open Base.mqh file and write the following code in the class block:
//+------------------------------------------------------------------+ //| Классы | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Класс-синглтон для общих флагов и событий графических элементов | //+------------------------------------------------------------------+ class CCommonManager { private: static CCommonManager *m_instance; // Экземпляр класса string m_element_name; // Имя активного элемента //--- Конструктор/деструктор CCommonManager(void) : m_element_name("") {} ~CCommonManager() {} public: //--- Метод для получения экземпляра Singleton static CCommonManager *GetInstance(void) { if(m_instance==NULL) m_instance=new CCommonManager(); return m_instance; } //--- Метод для уничтожения экземпляра Singleton static void DestroyInstance(void) { if(m_instance!=NULL) { delete m_instance; m_instance=NULL; } } //--- (1) Устанавливает, (2) возвращает имя активного текущего элемента void SetElementName(const string name) { this.m_element_name=name; } string ElementName(void) const { return this.m_element_name; } }; //--- Инициализация статической переменной экземпляра класса CCommonManager* CCommonManager::m_instance=NULL; //+------------------------------------------------------------------+ //| Базовый класс графических элементов | //+------------------------------------------------------------------+
A private class constructor and a static method for accessing an instance of the class ensures that only one instance exists in the application.
- A method static CCommonManager* GetInstance(void) — returns a pointer to a single instance of the class, creating it the first time it is accessed.
- A method static void DestroyInstance(void) — destroys an instance of the class and frees up memory.
- A method void SetElementName(const string name) — sets the name of the active graphic element.
- A method string ElementName(void) const — returns the name of the active graphic element.
Now each of the graphic elements can access an instance of this class to read and write the name of the active element. All elements share the same variable This ensures that each of the multiple objects reads and writes data to the same variable.
Since we cannot have more than one control active at the same time, such an implementation of the active element manager is quite sufficient without the access control functionality (so that two or more elements cannot write their data to a variable).
Later, other data can be added to this data manager class, for example, permission flags for the work chart. At the moment, each of the graphic elements is being created trying to remember the states of chart flags. This data can also be transferred to this class in the appropriate variables.
Classes for Organizing Auto-Repeat of Button Clicks
Above, we talked about creating an auto-repeat functionality for sending events from scrollbar buttons while holding the button for a long time. This behavior is standard for most OS applications. Therefore, I think there is no reason not to do the same here. When you press and hold the button, the time counter for holding the button starts first (usually this period is 350-500 ms). Further, if the button has not been released before the expiration of the hold time, the second counter is started — the counter for the interval of sending button press events. And such events are sent with a frequency of about 100 ms until the button is released.
To implement this behavior, implement two auxiliary classes — the millisecond timer class and the automatic event dispatch class.
Continue writing the code in the same file Base.mqh:
//+------------------------------------------------------------------+ //| Класс счётчика миллисекунд | //+------------------------------------------------------------------+ class CCounter : public CBaseObj { private: bool m_launched; // Флаг запущенного отсчёта //--- Запускает отсчёт void Run(const uint delay) { //--- Если отсчёт уже запущен - уходим if(this.m_launched) return; //--- Если передано ненулевое значение задержки - устанавливаем новое значение if(delay!=0) this.m_delay=delay; //--- Запоминаем время запуска и устанавливаем флаг, что отсчёт уже запущен this.m_start=::GetTickCount64(); this.m_launched=true; } protected: ulong m_start; // Время начала отсчёта uint m_delay; // Задержка public: //--- (1) Устанавливает задержку, запускает отсчёт с (2) установленной, (3) указанной задержкой void SetDelay(const uint delay) { this.m_delay=delay; } void Start(void) { this.Run(0); } void Start(const uint delay) { this.Run(delay); } //--- Возвращает флаг окончания отсчёта bool IsDone(void) { //--- Если отсчёт не запущен - возвращаем false if(!this.m_launched) return false; //--- Если прошло миллисекунд больше, чем время ожидания if(::GetTickCount64()-this.m_start>this.m_delay) { //--- сбрасываем флаг запущенного отсчёта и возвращаем true this.m_launched=false; return true; } //--- Заданное время ещё не прошло return false; } //--- Виртуальные методы (1) сохранения в файл, (2) загрузки из файла, (3) тип объекта virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_COUNTER); } //--- Конструктор/деструктор CCounter(void) : m_start(0), m_delay(0), m_launched(false) {} ~CCounter(void) {} }; //+------------------------------------------------------------------+ //| CCounter::Сохранение в файл | //+------------------------------------------------------------------+ bool CCounter::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CBaseObj::Save(file_handle)) return false; //--- Сохраняем значение задержки if(::FileWriteInteger(file_handle,this.m_delay,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CCounter::Загрузка из файла | //+------------------------------------------------------------------+ bool CCounter::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CBaseObj::Load(file_handle)) return false; //--- Загружаем значение задержки this.m_delay=::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
The millisecond timer class is designed to track the expiration of a specified time interval (delay) in milliseconds. It is inherited from the CBaseObj base class and can be used to implement timers, delays, and time control for various operations in MQL5 applications.
- A method void SetDelay(const uint delay) sets the delay value (in milliseconds).
- A method void Start(const uint delay) starts the countdown with a new delay.
- A method bool IsDone(void) returns true, if countdown is completed, otherwise - false.
- A method virtual bool Save(const int file_handle) is a virtual method for saving a state to a file.
- A method virtual bool Load(const int file_handle) is a virtual method for downloading a state from a file.
- A method virtual int Type(void) const returns the object type to identify in the system.
Based on this class, create an event auto-repeat class:
//+------------------------------------------------------------------+ //| Класс автоповтора событий | //+------------------------------------------------------------------+ class CAutoRepeat : public CBaseObj { private: CCounter m_delay_counter; // Счетчик для задержки перед автоповтором CCounter m_repeat_counter; // Счетчик для периодической отправки событий long m_chart_id; // График для отправки пользовательского события bool m_button_pressed; // Флаг, указывающий нажата ли кнопка bool m_auto_repeat_started; // Флаг, указывающий начался ли автоповтор uint m_delay_before_repeat; // Задержка перед началом автоповтора (мс) uint m_repeat_interval; // Периодичность отправки событий (мс) ushort m_event_id; // Идентификатор пользовательского события long m_event_lparam; // long-параметр пользовательского события double m_event_dparam; // double-параметр пользовательского события string m_event_sparam; // string-параметр пользовательского события //--- Отправка пользовательского события void SendEvent() { ::EventChartCustom((this.m_chart_id<=0 ? ::ChartID() : this.m_chart_id), this.m_event_id, this.m_event_lparam, this.m_event_dparam, this.m_event_sparam); } public: //--- Тип объекта virtual int Type(void) const { return(ELEMENT_TYPE_AUTOREPEAT_CONTROL); } //--- Конструкторы CAutoRepeat(void) : m_button_pressed(false), m_auto_repeat_started(false), m_delay_before_repeat(350), m_repeat_interval(100), m_event_id(0), m_event_lparam(0), m_event_dparam(0), m_event_sparam(""), m_chart_id(::ChartID()) {} CAutoRepeat(long chart_id, int delay_before_repeat=350, int repeat_interval=100, ushort event_id=0, long event_lparam=0, double event_dparam=0, string event_sparam="") : m_button_pressed(false), m_auto_repeat_started(false), m_delay_before_repeat(delay_before_repeat), m_repeat_interval(repeat_interval), m_event_id(event_id), m_event_lparam(event_lparam), m_event_dparam(event_dparam), m_event_sparam(event_sparam), m_chart_id(chart_id) {} //--- Установка идентификатора графика void SetChartID(const long chart_id) { this.m_chart_id=chart_id; } void SetDelay(const uint delay) { this.m_delay_before_repeat=delay; } void SetInterval(const uint interval) { this.m_repeat_interval=interval; } //--- Установка идентификатора и параметров пользовательского события void SetEvent(ushort event_id, long event_lparam, double event_dparam, string event_sparam) { this.m_event_id=event_id; this.m_event_lparam=event_lparam; this.m_event_dparam=event_dparam; this.m_event_sparam=event_sparam; } //--- Возврат флагов bool ButtonPressedFlag(void) const { return this.m_button_pressed; } bool AutorepeatStartedFlag(void) const { return this.m_auto_repeat_started;} uint Delay(void) const { return this.m_delay_before_repeat;} uint Interval(void) const { return this.m_repeat_interval; } //--- Обработка нажатия кнопки (запуск автоповтора) void OnButtonPress(void) { if(this.m_button_pressed) return; this.m_button_pressed=true; this.m_auto_repeat_started=false; this.m_delay_counter.Start(this.m_delay_before_repeat); // Запускаем счётчик ожидания } //--- Обработка отпускания кнопки (остановка автоповтора) void OnButtonRelease(void) { this.m_button_pressed=false; this.m_auto_repeat_started=false; } //--- Метод выполнения автоповтора (запускается в таймере) void Process(void) { //--- Если кнопка удерживается if(this.m_button_pressed) { //--- Проверяем, истекла ли задержка перед началом автоповтора if(!this.m_auto_repeat_started && this.m_delay_counter.IsDone()) { this.m_auto_repeat_started=true; this.m_repeat_counter.Start(this.m_repeat_interval); // Запускаем счетчик автоповтора } //--- Если автоповтор начался, проверяем периодичность отправки событий if(this.m_auto_repeat_started && this.m_repeat_counter.IsDone()) { //--- Отправляем событие и перезапускаем счетчик this.SendEvent(); this.m_repeat_counter.Start(this.m_repeat_interval); } } } };
This class allows you to send user events automatically with a set frequency while the button is held down. This ensures user-friendly interface behavior (as in standard OS scrollbars).
- The OnButtonPress() method is called when the button is pressed; it starts counting the delay before auto—repeat.
- The OnButtonRelease() method is called when the button gets released; it stops auto-repeat.
- The Process() method is the main method that should be called in the timer. It ensures that events are sent at the required frequency if the button is held down.
- The SetEvent(...) method — setting the parameters of a custom event.
- Methods SetDelay(...), setInterval(...) — setting the delay and auto-repeat interval.
Declare an object of the auto-repeat class in the base class of graphic element canvas CCanvasBase. Thus, it will be possible to use event auto-repeat in any object of graphic elements. It will be enough to set the delay and interval parameters and start auto-repeat in required situations.
Refining Base Classes
A lot of work has been done on the library to fix bugs and errors. Improvements affected almost every class. We will not describe every step of the work done here. But the key points will be announced, of course.
In Base.mqh declare all the classes we are going to implement today:
//+------------------------------------------------------------------+ //| 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 CCounter; // Класс счётчика задержки class CAutoRepeat; // Класс автоповтора событий class CImagePainter; // Класс рисования изображений class CLabel; // Класс текстовой метки class CButton; // Класс простой кнопки class CButtonTriggered; // Класс двухпозиционной кнопки class CButtonArrowUp; // Класс кнопки со стрелкой вверх class CButtonArrowDown; // Класс кнопки со стрелкой вниз class CButtonArrowLeft; // Класс кнопки со стрелкой влево class CButtonArrowRight; // Класс кнопки со стрелкой вправо class CCheckBox; // Класс элемента управления CheckBox class CRadioButton; // Класс элемента управления RadioButton class CScrollBarThumbH; // Класс ползунка горизонтальной полосы прокрутки class CScrollBarThumbV; // Класс ползунка вертикальной полосы прокрутки class CScrollBarH; // Класс горизонтальной полосы прокрутки class CScrollBarV; // Класс вертикальной полосы прокрутки class CPanel; // Класс элемента управления Panel class CGroupBox; // Класс элемента управления GroupBox class CContainer; // Класс элемента управления Container
Such a forward declaration of classes is necessary for error-free compilation of included Base.mqh and Controls.mqh files, since access to these classes was performed even before their actual declaration in the files.
Add new types to the enumeration of types of graphic elements and specify the range of constants of the types of objects that can participate in interaction with the user:
//+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_TYPE // Перечисление типов графических элементов { ELEMENT_TYPE_BASE = 0x10000, // Базовый объект графических элементов ELEMENT_TYPE_COLOR, // Объект цвета ELEMENT_TYPE_COLORS_ELEMENT, // Объект цветов элемента графического объекта ELEMENT_TYPE_RECTANGLE_AREA, // Прямоугольная область элемента ELEMENT_TYPE_IMAGE_PAINTER, // Объект для рисования изображений ELEMENT_TYPE_COUNTER, // Объект счётчика ELEMENT_TYPE_AUTOREPEAT_CONTROL, // Объект автоповтора событий ELEMENT_TYPE_CANVAS_BASE, // Базовый объект холста графических элементов ELEMENT_TYPE_ELEMENT_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 ELEMENT_TYPE_SCROLLBAR_THUMB_H, // Ползунок горизонтальной полосы прокрутки ELEMENT_TYPE_SCROLLBAR_THUMB_V, // Ползунок вертикальной полосы прокрутки ELEMENT_TYPE_SCROLLBAR_H, // Элемент управления ScrollBarHorisontal ELEMENT_TYPE_SCROLLBAR_V, // Элемент управления ScrollBarVertical ELEMENT_TYPE_PANEL, // Элемент управления Panel ELEMENT_TYPE_GROUPBOX, // Элемент управления GroupBox ELEMENT_TYPE_CONTAINER, // Элемент управления Container }; #define ACTIVE_ELEMENT_MIN ELEMENT_TYPE_LABEL // Минимальное значение списка активных элементов #define ACTIVE_ELEMENT_MAX ELEMENT_TYPE_SCROLLBAR_V // Максимальное значение списка активных элементов
When interacting with the mouse cursor, each graphical element is basically able to handle incoming event messages. But not every element should do this. In other words, it is necessary to check the type of the element and make a decision based on it — whether this element processes events or not. If following the path of checking object types, in the condition we get a long list of elements that cannot be handled. This is inconvenient. It's easier to add another property, a flag that indicates whether this element is active for interaction or static. Then we can only check this property to decide whether the event should be handled or not. Here we have specified the initial and final values of constants of graphic element types. When making a decision on event handling, it is enough only to check whether the element type is within this range of values. And on this basis a decision can be made.
Add an enumeration of properties by which you can sort base objects and search for them (CBaseObj):
enum ENUM_BASE_COMPARE_BY // Сравниваемые свойства базовых объектов { BASE_SORT_BY_ID = 0, // Сравнение базовых объектов по идентификатору BASE_SORT_BY_NAME, // Сравнение базовых объектов по имени BASE_SORT_BY_X, // Сравнение базовых объектов по координате X BASE_SORT_BY_Y, // Сравнение базовых объектов по координате Y BASE_SORT_BY_WIDTH, // Сравнение базовых объектов по ширине BASE_SORT_BY_HEIGHT, // Сравнение базовых объектов по высоте BASE_SORT_BY_ZORDER, // Сравнение по Z-order объектов };
Now all objects inherited from the base one can be sorted by the properties specified in the enumeration, if an object has such properties, which will add more flexibility when creating new descendant classes of CBaseObj.
In the function that returns the element type as a string add the output of letters "V" and "H" to the readable "Vertical" and "Horizontal":
//+------------------------------------------------------------------+ //| Возвращает тип элемента как строку | //+------------------------------------------------------------------+ string ElementDescription(const ENUM_ELEMENT_TYPE type) { string array[]; int total=StringSplit(EnumToString(type),StringGetCharacter("_",0),array); if(array[array.Size()-1]=="V") array[array.Size()-1]="Vertical"; if(array[array.Size()-1]=="H") array[array.Size()-1]="Horisontal"; string result=""; for(int i=2;i<total;i++) { array[i]+=" "; array[i].Lower(); array[i].SetChar(0,ushort(array[i].GetChar(0)-0x20)); result+=array[i]; } result.TrimLeft(); result.TrimRight(); return result; }
This will make the element descriptions more readable.
When creating new elements and adding them to the list of those attached to the container, object names will have to be created. Implement a function that returns short abbreviations of the types of elements that can be used to create an element, and then use this abbreviation in the object name to understand what kind of element it is:
//+------------------------------------------------------------------+ //| Возвращает короткое имя элемента по типу | //+------------------------------------------------------------------+ string ElementShortName(const ENUM_ELEMENT_TYPE type) { switch(type) { case ELEMENT_TYPE_ELEMENT_BASE : return "BASE"; // Базовый объект графических элементов case ELEMENT_TYPE_LABEL : return "LBL"; // Текстовая метка case ELEMENT_TYPE_BUTTON : return "SBTN"; // Простая кнопка case ELEMENT_TYPE_BUTTON_TRIGGERED : return "TBTN"; // Двухпозиционная кнопка case ELEMENT_TYPE_BUTTON_ARROW_UP : return "BTARU"; // Кнопка со стрелкой вверх case ELEMENT_TYPE_BUTTON_ARROW_DOWN : return "BTARD"; // Кнопка со стрелкой вниз case ELEMENT_TYPE_BUTTON_ARROW_LEFT : return "BTARL"; // Кнопка со стрелкой влево case ELEMENT_TYPE_BUTTON_ARROW_RIGHT: return "BTARR"; // Кнопка со стрелкой вправо case ELEMENT_TYPE_CHECKBOX : return "CHKB"; // Элемент управления CheckBox case ELEMENT_TYPE_RADIOBUTTON : return "RBTN"; // Элемент управления RadioButton case ELEMENT_TYPE_SCROLLBAR_THUMB_H : return "THMBH"; // Ползунок горизонтальной полосы прокрутки case ELEMENT_TYPE_SCROLLBAR_THUMB_V : return "THMBV"; // Ползунок вертикальной полосы прокрутки case ELEMENT_TYPE_SCROLLBAR_H : return "SCBH"; // Элемент управления ScrollBarHorisontal case ELEMENT_TYPE_SCROLLBAR_V : return "SCBV"; // Элемент управления ScrollBarVertical case ELEMENT_TYPE_PANEL : return "PNL"; // Элемент управления Panel case ELEMENT_TYPE_GROUPBOX : return "GRBX"; // Элемент управления GroupBox case ELEMENT_TYPE_CONTAINER : return "CNTR"; // Элемент управления Container default : return "Unknown"; // Unknown } }
When attaching elements to a container, their names will be realised subject to the hierarchy of objects: "container -- an attached element to the container --- an attached element to an attached element", etc.
Delimiters between names of the elements in the name string will be underscores ("_"). We can use the full name to create a list of names for the entire hierarchy of objects. To do this, implement the following function:
//+------------------------------------------------------------------+ //| Возвращает массив имён иерархии элементов | //+------------------------------------------------------------------+ int GetElementNames(string value, string sep, string &array[]) { if(value=="" || value==NULL) { PrintFormat("%s: Error. Empty string passed"); return 0; } ResetLastError(); int res=StringSplit(value, StringGetCharacter(sep,0),array); if(res==WRONG_VALUE) { PrintFormat("%s: StringSplit() failed. Error %d",__FUNCTION__, GetLastError()); return WRONG_VALUE; } return res; }
The function returns the number of objects in the hierarchy and fills in the array of names of all the elements.
In the CBound rectangular area class, write a method for comparing two objects:
//+------------------------------------------------------------------+ //| CBound::Сравнение двух объектов | //+------------------------------------------------------------------+ int CBound::Compare(const CObject *node,const int mode=0) const { if(node==NULL) return -1; const CBound *obj=node; switch(mode) { case BASE_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case BASE_SORT_BY_X : return(this.X() >obj.X() ? 1 : this.X() <obj.X() ? -1 : 0); case BASE_SORT_BY_Y : return(this.Y() >obj.Y() ? 1 : this.Y() <obj.Y() ? -1 : 0); case BASE_SORT_BY_WIDTH : return(this.Width() >obj.Width() ? 1 : this.Width() <obj.Width() ? -1 : 0); case BASE_SORT_BY_HEIGHT: return(this.Height() >obj.Height() ? 1 : this.Height() <obj.Height() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
Previously, the comparison was carried out using the parent class method of the same name. This enabled to compare only two properties: object’s name and identifier.
The lion’s share of improvements concerned the base class of CCanvasBase graphic element canvas object, since it is it that accumulates the main properties of all graphic elements.
In the protected section of the class, declare new variables and three methods for working with the shared resource manager:
//+------------------------------------------------------------------+ //| Базовый класс холста графических элементов | //+------------------------------------------------------------------+ class CCanvasBase : public CBaseObj { private: bool m_chart_mouse_wheel_flag; // Флаг отправки сообщений о прокрутке колёсика мышки bool m_chart_mouse_move_flag; // Флаг отправки сообщений о перемещениях курсора мышки bool m_chart_object_create_flag; // Флаг отправки сообщений о событии создания графического объекта bool m_chart_mouse_scroll_flag; // Флаг прокрутки графика левой кнопкой и колёсиком мышки bool m_chart_context_menu_flag; // Флаг доступа к контекстному меню по нажатию правой клавиши мышки bool m_chart_crosshair_tool_flag; // Флаг доступа к инструменту "Перекрестие" по нажатию средней клавиши мышки bool m_flags_state; // Состояние флагов прокрутки графика колёсиком, контекстного меню и перекрестия //--- Установка запретов для графика (прокрутка колёсиком, контекстное меню и перекрестие) void SetFlags(const bool flag); protected: 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; // Объект управления цветом рамки активированного элемента CAutoRepeat m_autorepeat; // Объект управления автоповторами событий 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_lt; // Ширина рамки слева uint m_border_width_rt; // Ширина рамки справа uint m_border_width_up; // Ширина рамки сверху uint m_border_width_dn; // Ширина рамки снизу string m_program_name; // Имя программы bool m_hidden; // Флаг скрытого объекта bool m_blocked; // Флаг заблокированного элемента bool m_movable; // Флаг перемещаемого элемента bool m_focused; // Флаг элемента в фокусе bool m_main; // Флаг главного объекта bool m_autorepeat_flag; // Флаг автоповтора отправки событий bool m_scroll_flag; // Флаг прокрутки содержимого при помощи скроллбаров bool m_trim_flag; // Флаг обрезки элемента по границам контейнера int m_cursor_delta_x; // Дистанция от курсора до левого края элемента int m_cursor_delta_y; // Дистанция от курсора до верхнего края элемента int m_z_order; // Z-ордер графического объекта //--- (1) Устанавливает, возвращает (2) имя, (3) флаг активного элемента void SetActiveElementName(const string name) { CCommonManager::GetInstance().SetElementName(name); } string ActiveElementName(void) const { return CCommonManager::GetInstance().ElementName(); } bool IsCurrentActiveElement(void) const { return this.ActiveElementName()==this.NameFG(); } //--- Возврат смещения начальных координат рисования на холсте относительно канваса и координат объекта int CanvasOffsetX(void) const { return(this.ObjectX()-this.X()); } int CanvasOffsetY(void) const { return(this.ObjectY()-this.Y()); } //--- Возвращает скорректированную координату точки на холсте с учётом смещения холста относительно объекта int AdjX(const int x) const { return(x-this.CanvasOffsetX()); } int AdjY(const int y) const { return(y-this.CanvasOffsetY()); } //--- Возвращает скорректированный идентификатор графика long CorrectChartID(const long chart_id) const { return(chart_id!=0 ? chart_id : ::ChartID()); } public:
- CAutoRepeat m_autorepeat — an event auto-repeat object; any of the graphic elements can have functionality provided by the class of this object.
- uint m_border_width_lt — border width on the left; the border is the boundary of the visible area of the container, and the indentation of the visible area from the edge of the element can be of different sizes on different sides.
- uint m_border_width_rt — border width on the right.
- uint m_border_width_up — border width on the top.
- uint m_border_width_dn — border width on the bottom.
- bool m_movable — a flag for the object to be moved; for example, a button is a non—movable element, a scrollbar thumb is a movable one, etc.
- bool m_main — the flag of the main element; the main element is the very first one in the hierarchy of linked objects, for example, a panel on which other controls are located; this is usually a form object.
- bool m_autorepeat_flag — the flag for using an event auto-repeat by the element.
- bool m_scroll_flag — the flag for scrolling an element with scroll bars.
- bool m_trim_flag — the flag for cropping an element at the edges of container’s visible area; for example, scrollbars are outside the visible area of the container, but are not cropped at its edges.
- int m_cursor_delta_x — an auxiliary variable that stores the cursor's distance from the left bound of the element.
- int m_cursor_delta_y — an auxiliary variable that stores the cursor's distance from the upper bound of the element.
- int m_z_order — priority of a graphic object to receive a mouse click event on the chart; when objects overlap, the CHARTEVENT_CLICK event will retrieve only one object which priority is higher than that of others.
- A method void SetActiveElementName(const string name) — sets the name of the currently active element in the shared data manager.
- A method string ActiveElementName(void) — returns the name of the current active element.
- A method bool IsCurrentActiveElement(void) — returns a flag indicating that this object is currently active.
In the protected section of the class, add handlers for moving the mouse cursor and changing the control:
//--- Обработчики событий (1) наведения курсора (Focus), (2) нажатий кнопок мышки (Press), (3) перемещения курсора (Move), //--- (4) прокрутки колёсика (Wheel), (5) ухода из фокуса (Release), (6) создания графического объекта (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 OnMoveEvent(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; } // обработчик здесь отключен virtual void ObjectChangeHandler(const int id, const long lparam, const double dparam, const string sparam) { return; } // обработчик здесь отключен
When moving the cursor over an object, such events must be handled, and some controls will later be able to resize with the mouse. We have announced the handlers of such events here.
In the public section of the class, add methods for working with some variables of the class:
public: //--- Возвращает указатель на (1) контейнер, (2) объект класса автоповтора событий CCanvasBase *GetContainer(void) const { return this.m_container; } CAutoRepeat *GetAutorepeatObj(void) { return &this.m_autorepeat; }
...
//--- (1) Устанавливает, (2) возвращает z-ордер bool ObjectSetZOrder(const int value); int ObjectZOrder(void) const { return this.m_z_order; } //--- Возвращает (1) принадлежность объекта программе, флаг (2) скрытого, (3) заблокированного, //--- (4) перемещаемого, (5) главного элемента, (6) в фокусе, (7) имя графического объекта (фон, текст) bool IsBelongsToThis(const string name) const { return(::ObjectGetString(this.m_chart_id,name,OBJPROP_TEXT)==this.m_program_name);} bool IsHidden(void) const { return this.m_hidden; } bool IsBlocked(void) const { return this.m_blocked; } bool IsMovable(void) const { return this.m_movable; } bool IsMain(void) const { return this.m_main; } bool IsFocused(void) const { return this.m_focused; } string NameBG(void) const { return this.m_background.ChartObjectName(); } string NameFG(void) const { return this.m_foreground.ChartObjectName(); }
...
//--- (1) Возвращает, (2) устанавливает ширину рамки слева
uint BorderWidthLeft(void) const { return this.m_border_width_lt; }
void SetBorderWidthLeft(const uint width) { this.m_border_width_lt=width; }
//--- (1) Возвращает, (2) устанавливает ширину рамки справа
uint BorderWidthRight(void) const { return this.m_border_width_rt; }
void SetBorderWidthRight(const uint width) { this.m_border_width_rt=width; }
//--- (1) Возвращает, (2) устанавливает ширину рамки сверху
uint BorderWidthTop(void) const { return this.m_border_width_up; }
void SetBorderWidthTop(const uint width) { this.m_border_width_up=width; }
//--- (1) Возвращает, (2) устанавливает ширину рамки снизу
uint BorderWidthBottom(void) const { return this.m_border_width_dn; }
void SetBorderWidthBottom(const uint width) { this.m_border_width_dn=width; }
//--- Устанавливает одинаковую ширину рамки со всех сторон
void SetBorderWidth(const uint width)
{
this.m_border_width_lt=this.m_border_width_rt=this.m_border_width_up=this.m_border_width_dn=width;
}
//--- Устанавливает ширину рамки
void SetBorderWidth(const uint left,const uint right,const uint top,const uint bottom)
{
this.m_border_width_lt=left;
this.m_border_width_rt=right;
this.m_border_width_up=top;
this.m_border_width_dn=bottom;
}
...
Some methods should be made virtual, as they must work differently for different elements.
//--- Устанавливает объекту флаг (1) перемещаемости, (2) главного объекта void SetMovable(const bool flag) { this.m_movable=flag; } void SetAsMain(void) { this.m_main=true; } //--- Ограничивает графический объект по размерам контейнера virtual bool ObjectTrim(void); //--- Изменяет размеры объекта virtual bool ResizeW(const int w); virtual bool ResizeH(const int h); virtual bool Resize(const int w,const int h); //--- Устанавливает объекту новую координату (1) X, (2) Y, (3) XY virtual bool MoveX(const int x); virtual bool MoveY(const int y); virtual bool Move(const int x,const int y); //--- Смещает объект по оси (1) X, (2) Y, (3) XY на указанное смещение virtual bool ShiftX(const int dx); virtual bool ShiftY(const int dy); virtual bool Shift(const int dx,const int dy);
...
//--- Обработчик событий virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- (1) Таймер, (2) обработчик события таймера virtual void OnTimer() { this.TimerEventHandler(); } virtual void TimerEventHandler(void) { return; }
In class constructors, all new variables are initialized with default values:
//--- Конструкторы/деструктор 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_movable(false), m_main(false), m_autorepeat_flag(false), m_trim_flag(true), m_scroll_flag(false), m_border_width_lt(0), m_border_width_rt(0), m_border_width_up(0), m_border_width_dn(0), m_z_order(0), m_state(0), m_wnd_y(0), m_cursor_delta_x(0), m_cursor_delta_y(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); }; //+------------------------------------------------------------------+ //| CCanvasBase::Конструктор | //+------------------------------------------------------------------+ CCanvasBase::CCanvasBase(const string object_name,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : m_program_name(::MQLInfoString(MQL_PROGRAM_NAME)), m_wnd(wnd<0 ? 0 : wnd), m_alpha_bg(0), m_alpha_fg(255), m_hidden(false), m_blocked(false), m_focused(false), m_movable(false), m_main(false), m_autorepeat_flag(false), m_trim_flag(true), m_scroll_flag(false), m_border_width_lt(0), m_border_width_rt(0), m_border_width_up(0), m_border_width_dn(0), m_z_order(0), m_state(0), m_cursor_delta_x(0), m_cursor_delta_y(0) { ...
Add implementation of the virtual Compare method:
//+------------------------------------------------------------------+ //| CCanvasBase::Сравнение двух объектов | //+------------------------------------------------------------------+ int CCanvasBase::Compare(const CObject *node,const int mode=0) const { if(node==NULL) return -1; const CCanvasBase *obj=node; switch(mode) { case BASE_SORT_BY_NAME : return(this.Name() >obj.Name() ? 1 : this.Name() <obj.Name() ? -1 : 0); case BASE_SORT_BY_X : return(this.X() >obj.X() ? 1 : this.X() <obj.X() ? -1 : 0); case BASE_SORT_BY_Y : return(this.Y() >obj.Y() ? 1 : this.Y() <obj.Y() ? -1 : 0); case BASE_SORT_BY_WIDTH : return(this.Width() >obj.Width() ? 1 : this.Width() <obj.Width() ? -1 : 0); case BASE_SORT_BY_HEIGHT: return(this.Height() >obj.Height() ? 1 : this.Height() <obj.Height() ? -1 : 0); case BASE_SORT_BY_ZORDER: return(this.ObjectZOrder() >obj.ObjectZOrder() ? 1 : this.ObjectZOrder() <obj.ObjectZOrder() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
Refine a method that trims a graphic object along the container contour:
//+------------------------------------------------------------------+ //| CCanvasBase::Подрезает графический объект по контуру контейнера | //+------------------------------------------------------------------+ bool CCanvasBase::ObjectTrim() { //--- Проверяем флаг разрешения обрезки элемента и, //--- если элемент не должен обрезаться по границам контейнера - возвращаем false if(!this.m_trim_flag) return false; //--- Получаем границы контейнера int container_left = this.ContainerLimitLeft(); int container_right = this.ContainerLimitRight(); int container_top = this.ContainerLimitTop(); int container_bottom = this.ContainerLimitBottom(); //--- Получаем текущие границы объекта int object_left = this.X(); int object_right = this.Right(); int object_top = this.Y(); int object_bottom = this.Bottom(); //--- Проверяем, полностью ли объект выходит за пределы контейнера и, если да - скрываем его if(object_right <= container_left || object_left >= container_right || object_bottom <= container_top || object_top >= container_bottom) { this.Hide(true); if(this.ObjectResize(this.Width(),this.Height())) this.BoundResize(this.Width(),this.Height()); return false; } //--- Объект полностью или частично находится внутри видимой области контейнера else { //--- Если элемент полностью внутри контейнера if(object_right<=container_right && object_left>=container_left && object_bottom<=container_bottom && object_top>=container_top) { //--- Если ширина или высота графического объекта не совпадает с шириной или высотой элемента, //--- модифицируем графический объект по размерам элемента и возвращаем true if(this.ObjectWidth()!=this.Width() || this.ObjectHeight()!=this.Height()) { if(this.ObjectResize(this.Width(),this.Height())) return true; } } //--- Если элемент частично находится в видимой области контейнера else { //--- Если элемент по вертикали находится в видимой области контейнера if(object_bottom<=container_bottom && object_top>=container_top) { //--- Если высота графического объекта не совпадает с высотой элемента, //--- модифицируем графический объект по высоте элемента if(this.ObjectHeight()!=this.Height()) this.ObjectResizeH(this.Height()); } else { //--- Если элемент по горизонтали находится в видимой области контейнера if(object_right<=container_right && object_left>=container_left) { //--- Если ширина графического объекта не совпадает с шириной элемента, //--- модифицируем графический объект по ширине элемента if(this.ObjectWidth()!=this.Width()) this.ObjectResizeW(this.Width()); } } } } //--- Проверяем выход объекта по горизонтали и вертикали за пределы контейнера bool modified_horizontal=false; // Флаг изменений по горизонтали bool modified_vertical =false; // Флаг изменений по вертикали //--- Обрезка по горизонтали int new_left = object_left; int new_width = this.Width(); //--- Если объект выходит за левую границу контейнера if(object_left<=container_left) { int crop_left=container_left-object_left; new_left=container_left; new_width-=crop_left; modified_horizontal=true; } //--- Если объект выходит за правую границу контейнера if(object_right>=container_right) { int crop_right=object_right-container_right; new_width-=crop_right; modified_horizontal=true; } //--- Если были изменения по горизонтали if(modified_horizontal) { this.ObjectSetX(new_left); this.ObjectResizeW(new_width); } //--- Обрезка по вертикали int new_top=object_top; int new_height=this.Height(); //--- Если объект выходит за верхнюю границу контейнера if(object_top<=container_top) { int crop_top=container_top-object_top; new_top=container_top; new_height-=crop_top; modified_vertical=true; } //--- Если объект выходит за нижнюю границу контейнера if(object_bottom>=container_bottom) { int crop_bottom=object_bottom-container_bottom; new_height-=crop_bottom; modified_vertical=true; } //--- Если были изменения по вертикали if(modified_vertical) { this.ObjectSetY(new_top); this.ObjectResizeH(new_height); } //--- После рассчётов, объект может быть скрыт, но теперь находится в области контейнера - отображаем его this.Show(false); //--- Если объект был изменен, перерисовываем его if(modified_horizontal || modified_vertical) { this.Update(false); this.Draw(false); return true; } return false; }
First of all, the method was implemented with the bool type in order to be able to understand the need to redraw the chart after the method has run. In various testing modes, a flaw in the method was discovered. It manifested itself in the fact that the trimmed elements did not restore their dimensions. This happened if the element went beyond the container bounaries and then it again returned to the visible area of the container. An element is cropped by changing the coordinates and dimensions of its graphic object. After the dimensions were changed, they never restored again. Now, this is fixed.
A method that sets the z-order of a graphic object:
//+------------------------------------------------------------------+ //| CCanvasBase::Устанавливает z-ордер графического объекта | //+------------------------------------------------------------------+ bool CCanvasBase::ObjectSetZOrder(const int value) { //--- Если передано уже установленное значение - возвращаем true if(this.ObjectZOrder()==value) return true; //--- Если не удалось установить новое значение в графические объекты фона и переднего плана - возвращаем false if(!::ObjectSetInteger(this.m_chart_id,this.NameBG(),OBJPROP_ZORDER,value) || !::ObjectSetInteger(this.m_chart_id,this.NameFG(),OBJPROP_ZORDER,value)) return false; //--- Записываем новое значение z-ордер в переменную и возвращаем true this.m_z_order=value; return true; }
First, the passed z-order value is set to the background and foreground graphic objects, then to a variable. If the value could not be set in the graphic objects, then leave the method with the return of false.
Methods for resizing a graphic element:
//+------------------------------------------------------------------+ //| CCanvasBase::Изменяет ширину объекта | //+------------------------------------------------------------------+ bool CCanvasBase::ResizeW(const int w) { if(!this.ObjectResizeW(w)) return false; this.BoundResizeW(w); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } return true; } //+------------------------------------------------------------------+ //| CCanvasBase::Изменяет высоту объекта | //+------------------------------------------------------------------+ bool CCanvasBase::ResizeH(const int h) { if(!this.ObjectResizeH(h)) return false; this.BoundResizeH(h); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } return true; } //+------------------------------------------------------------------+ //| CCanvasBase::Изменяет размеры объекта | //+------------------------------------------------------------------+ bool CCanvasBase::Resize(const int w,const int h) { if(!this.ObjectResize(w,h)) return false; this.BoundResize(w,h); if(!this.ObjectTrim()) { this.Update(false); this.Draw(false); } return true; }
If the physical size of the graphic object could not be changed, the method returns false. Upon successful resizing of the graphic object, we set new values to the rectangular area object that describes the size of the element and call the method for cropping the element along container bounds. If the ObjectTrim method returns false, it means either it has not changed anything in the object, or this object is not modifiable. In this case, the object still should be updated and redrawn, but without redrawing the chart. Finally, return true.
In the methods of moving an element, the unmodifiable coordinate must be adjusted according to its actual location relative to the container, which is what the AdjX and AdjY methods do:
//+------------------------------------------------------------------+ //| CCanvasBase::Устанавливает объекту новую координату X | //+------------------------------------------------------------------+ bool CCanvasBase::MoveX(const int x) { return this.Move(x,this.AdjY(this.ObjectY())); } //+------------------------------------------------------------------+ //| CCanvasBase::Устанавливает объекту новую координату Y | //+------------------------------------------------------------------+ bool CCanvasBase::MoveY(const int y) { return this.Move(this.AdjX(this.ObjectX()),y); }
In the class initialization method initialize the millisecond timer:
//+------------------------------------------------------------------+ //| CCanvasBase::Инициализация класса | //+------------------------------------------------------------------+ void CCanvasBase::Init(void) { //--- Запоминаем разрешения для мышки и инструментов графика this.m_chart_mouse_wheel_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL); this.m_chart_mouse_move_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE); this.m_chart_object_create_flag = ::ChartGetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE); this.m_chart_mouse_scroll_flag = ::ChartGetInteger(this.m_chart_id, CHART_MOUSE_SCROLL); this.m_chart_context_menu_flag = ::ChartGetInteger(this.m_chart_id, CHART_CONTEXT_MENU); this.m_chart_crosshair_tool_flag= ::ChartGetInteger(this.m_chart_id, CHART_CROSSHAIR_TOOL); //--- Устанавливаем разрешения для мышки и графика ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_WHEEL, true); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_MOUSE_MOVE, true); ::ChartSetInteger(this.m_chart_id, CHART_EVENT_OBJECT_CREATE, true); //--- Инициализируем цвета объекта по умолчанию this.InitColors(); //--- Инициализируем миллисекундный таймер ::EventSetMillisecondTimer(16); }
Now, refine the class event handler. Practice user's usual behavior, as in the operating system. When you hover the cursor over the active element, its color should change, and when you move the cursor away, it should return to its original color. When you click on an element, its color also changes and the element is ready for interaction. If it is a simple button, then releasing the mouse button in the button area will generate a click event. If you move the cursor away from the object while the button is pressed and held down, its color will change, and when the button is released, a click event will not be generated. If it is a movable element, then holding down the button and moving the cursor will cause the held element to move. And it doesn't matter if the cursor is on the object or outside it at the moment of holding, the element will move until the mouse button is released.
Let's see what improvements have been made to the class event handler for this:
//+------------------------------------------------------------------+ //| 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(id==CHARTEVENT_OBJECT_CREATE) { this.OnCreateEvent(id,lparam,dparam,sparam); } //--- Если элемент заблокирован или скрыт - уходим if(this.IsBlocked() || this.IsHidden()) return; //--- Координаты курсора мышки int x=(int)lparam; int y=(int)dparam-this.m_wnd_y; // Корректируем Y по высоте окна индикатора //--- Событие перемещения курсора if(id==CHARTEVENT_MOUSE_MOVE) { //--- Неактивные элементы, кроме главного, не обрабатываем if(!this.IsMain() && (this.Type()<ACTIVE_ELEMENT_MIN || this.Type()>ACTIVE_ELEMENT_MAX)) return; //--- Кнопка мышки удерживается if(sparam=="1") { //--- Курсор в пределах объекта if(this.Contains(x, y)) { //--- Если это главный объект - запрещаем инструменты графика if(this.IsMain()) this.SetFlags(false); //--- Если кнопка мышки была зажата на графике - обрабатывать нечего, уходим if(this.ActiveElementName()=="Chart") return; //--- Фиксируем имя активного элемента, над которым был курсор при нажатии кнопки мышки this.SetActiveElementName(this.ActiveElementName()); //--- Если это текущий активный элемент - обрабатываем его перемещение if(this.IsCurrentActiveElement()) { this.OnMoveEvent(id,lparam,dparam,sparam); //--- Если у элемента активен автоповтор событий - указываем, что кнопка нажата if(this.m_autorepeat_flag) this.m_autorepeat.OnButtonPress(); } } //--- Курсор за пределами объекта else { //--- Если это активный главный объект, либо кнопка мышки зажата на графике - разрешаем инструменты графика if(this.IsMain() && (this.ActiveElementName()==this.NameFG() || this.ActiveElementName()=="Chart")) this.SetFlags(true); //--- Если это текущий активный элемент if(this.IsCurrentActiveElement()) { //--- Если элемент неперемещаемый if(!this.IsMovable()) { //--- вызываем обработчик наведения курсора мышки this.OnFocusEvent(id,lparam,dparam,sparam); //--- Если у элемента активен автоповтор событий - указываем, что кнопка отжата if(this.m_autorepeat_flag) this.m_autorepeat.OnButtonRelease(); } //--- Если элемент перемещаемый - вызываем обработчик перемещения else this.OnMoveEvent(id,lparam,dparam,sparam); } } } //--- Кнопка мышки не нажата else { //--- Курсор в пределах объекта if(this.Contains(x, y)) { //--- Если это главный элемент - отключаем инструменты графика if(this.IsMain()) this.SetFlags(false); //--- Вызываем обработчик наведения курсора и //--- устанавливаем элемент как текущий активный this.OnFocusEvent(id,lparam,dparam,sparam); this.SetActiveElementName(this.NameFG()); } //--- Курсор за пределами объекта else { //--- Если это главный объект if(this.IsMain()) { //--- Разрешаем инструменты графика и //--- устанавливаем график как текущий активный элемент this.SetFlags(true); this.SetActiveElementName("Chart"); } //--- Вызываем обработчик увода курсора из фокуса this.OnReleaseEvent(id,lparam,dparam,sparam); } } } //--- Событие щелчка кнопкой мышки на объекте (отпускание кнопки) if(id==CHARTEVENT_OBJECT_CLICK) { //--- Если щелчок (отпускание кнопки мышки) был по этому объекту if(sparam==this.NameFG()) { //--- Вызываем обработчик щелчка мышки и освобождаем текущий активный объект this.OnPressEvent(id, lparam, dparam, sparam); this.SetActiveElementName(""); //--- Если у элемента активен автоповтор событий - указываем, что кнопка отжата if(this.m_autorepeat_flag) this.m_autorepeat.OnButtonRelease(); } } //--- Событие прокрутки колёсика мышки if(id==CHARTEVENT_MOUSE_WHEEL) { if(this.IsCurrentActiveElement()) this.OnWheelEvent(id,lparam,dparam,sparam); } //--- Если пришло пользовательское событие графика if(id>CHARTEVENT_CUSTOM) { //--- собственные события не обрабатываем if(sparam==this.NameFG()) 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); } //--- Если изменение графического элемента - вызываем обработчик пользовательского события if(chart_event==CHARTEVENT_OBJECT_CHANGE) { this.ObjectChangeHandler(chart_event, lparam, dparam, sparam); } } }
The entire logic of the method is described in the comments to the code and currently corresponds to the stated functionality.
Cursor Move Handler:
//+------------------------------------------------------------------+ //| CCanvasBase::Обработчик перемещения курсора | //+------------------------------------------------------------------+ void CCanvasBase::OnMoveEvent(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); } //--- Рассчитываем отступ курсора от верхнего левого угла элемента по осям X и Y if(this.m_cursor_delta_x==0) this.m_cursor_delta_x=(int)lparam-this.X(); if(this.m_cursor_delta_y==0) this.m_cursor_delta_y=(int)::round(dparam-this.Y()); }
When holding the mouse button on the element, set the flag that the element is in the pressed state. Change the element color and calculate the cursor distance from the upper-left corner of the element by two axes. These offsets will be used when handling the movement of the mouse cursor — so that the object shifts after the cursor, anchoring to it not with the origin point (upper-left corner), but with the indentation recorded in these variables.
In handlers of cursor focus, leaving focus, and clicking on an object, initialize the indentation values with zeros:
//+------------------------------------------------------------------+ //| 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); } //--- Инициализируем отступ курсора от верхнего левого угла элемента по осям X и Y this.m_cursor_delta_x=0; this.m_cursor_delta_y=0; } //+------------------------------------------------------------------+ //| 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); } //--- Инициализируем отступ курсора от верхнего левого угла элемента по осям X и Y this.m_cursor_delta_x=0; this.m_cursor_delta_y=0; } //+------------------------------------------------------------------+ //| 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); } //--- Инициализируем отступ курсора от верхнего левого угла элемента по осям X и Y this.m_cursor_delta_x=0; this.m_cursor_delta_y=0; //--- отправляем пользовательское событие на график с переданными значениями в lparam, dparam, и именем объекта в sparam ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_OBJECT_CLICK, lparam, dparam, this.NameFG()); }
In addition to initializing variables, in the click handler, to the chart send a custom click event on an element with the name of the foreground graphic object.
Such changes (and some other minor but mandatory ones) affected the file with classes of base objects.
Now, refine the file Controls.mqh of graphic elements classes.
Add new macro substitutions and constants of enumeration of graphic element properties:
//+------------------------------------------------------------------+ //| Макроподстановки | //+------------------------------------------------------------------+ #define DEF_LABEL_W 50 // Ширина текстовой метки по умолчанию #define DEF_LABEL_H 16 // Высота текстовой метки по умолчанию #define DEF_BUTTON_W 60 // Ширина кнопки по умолчанию #define DEF_BUTTON_H 16 // Высота кнопки по умолчанию #define DEF_PANEL_W 80 // Ширина панели по умолчанию #define DEF_PANEL_H 80 // Высота панели по умолчанию #define DEF_SCROLLBAR_TH 13 // Толщина полосы прокрутки по умолчанию #define DEF_THUMB_MIN_SIZE 8 // Минимальная толщина ползунка полосы прокрутки #define DEF_AUTOREPEAT_DELAY 500 // Задержка перед запуском автоповтора #define DEF_AUTOREPEAT_INTERVAL 100 // Частота автоповторов //+------------------------------------------------------------------+ //| Перечисления | //+------------------------------------------------------------------+ enum ENUM_ELEMENT_SORT_BY // Сравниваемые свойства { ELEMENT_SORT_BY_ID = BASE_SORT_BY_ID, // Сравнение по идентификатору элемента ELEMENT_SORT_BY_NAME = BASE_SORT_BY_NAME, // Сравнение по наименованию элемента ELEMENT_SORT_BY_X = BASE_SORT_BY_X, // Сравнение по координате X элемента ELEMENT_SORT_BY_Y = BASE_SORT_BY_Y, // Сравнение по координате Y элемента ELEMENT_SORT_BY_WIDTH= BASE_SORT_BY_WIDTH, // Сравнение по ширине элемента ELEMENT_SORT_BY_HEIGHT= BASE_SORT_BY_HEIGHT, // Сравнение по высоте элемента ELEMENT_SORT_BY_ZORDER= BASE_SORT_BY_ZORDER, // Сравнение по Z-order элемента ELEMENT_SORT_BY_TEXT, // Сравнение по тексту элемента ELEMENT_SORT_BY_COLOR_BG, // Сравнение по цвету фона элемента ELEMENT_SORT_BY_ALPHA_BG, // Сравнение по прозрачности фона элемента ELEMENT_SORT_BY_COLOR_FG, // Сравнение по цвету переднего плана элемента ELEMENT_SORT_BY_ALPHA_FG, // Сравнение по прозрачности переднего плана элемента ELEMENT_SORT_BY_STATE, // Сравнение по состоянию элемента ELEMENT_SORT_BY_GROUP, // Сравнение по группе элемента };
The first seven constants correspond to the similar constants of enumeration of base object properties. Thus, this enumeration continues the list of properties of the base object.
In the CImagePainter image drawing class, declare a new method for drawing the borders of a group of elements:
//--- Очищает область 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); //--- Рисует рамку группы элементов bool FrameGroupElements(const int x,const int y,const int w,const int h,const string text, const color clr_text,const color clr_dark,const color clr_light, const uchar alpha,const bool update=true); //--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
and outside of the class body, write its implementation:
//+------------------------------------------------------------------+ //| Рисует рамку группы элементов | //+------------------------------------------------------------------+ bool CImagePainter::FrameGroupElements(const int x,const int y,const int w,const int h,const string text, const color clr_text,const color clr_dark,const color clr_light, const uchar alpha,const bool update=true) { //--- Если область изображения не валидна - возвращаем false if(!this.CheckBound()) return false; //--- Корректировка координаты Y int tw=0, th=0; if(text!="" && text!=NULL) this.m_canvas.TextSize(text,tw,th); int shift_v=int(th!=0 ? ::ceil(th/2) : 0); //--- Координаты и размеры рамки int x1=x; // Левый верхний угол области рамки, X int y1=y+shift_v; // Левый верхний угол области рамки, Y int x2=x+w-1; // Правый нижний угол области рамки, X int y2=y+h-1; // Правый нижний угол области рамки, Y //--- Рисуем левую-верхнюю часть рамки int arrx[3], arry[3]; arrx[0]=arrx[1]=x1; arrx[2]=x2-1; arry[0]=y2; arry[1]=arry[2]=y1; this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr_dark, alpha)); arrx[0]++; arrx[1]++; arry[1]++; arry[2]++; this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr_light, alpha)); //--- Рисуем правую-нижнюю часть рамки arrx[0]=arrx[1]=x2-1; arrx[2]=x1+1; arry[0]=y1; arry[1]=arry[2]=y2-1; this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr_dark, alpha)); arrx[0]++; arrx[1]++; arry[1]++; arry[2]++; this.m_canvas.Polyline(arrx, arry, ::ColorToARGB(clr_light, alpha)); if(tw>0) this.m_canvas.FillRectangle(x+5,y,x+7+tw,y+th,clrNULL); this.m_canvas.TextOut(x+6,y-1,text,::ColorToARGB(clr_text, alpha)); if(update) this.m_canvas.Update(false); return true; }
The method draws an embossed border in two colors, which are passed to the method — light and dark. If an empty text is passed, then a border is simply drawn around the perimeter of the group object. If the text contains anything, then the upper part of the border is drawn below the upper boundary of the group by half the height of the text.
Refine the method for comparing the image drawing class:
//+------------------------------------------------------------------+ //| CImagePainter::Сравнение двух объектов | //+------------------------------------------------------------------+ int CImagePainter::Compare(const CObject *node,const int mode=0) const { if(node==NULL) return -1; 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_FG : case ELEMENT_SORT_BY_ALPHA_BG : return(this.Alpha() >obj.Alpha() ? 1 : this.Alpha() <obj.Alpha() ? -1 : 0); case ELEMENT_SORT_BY_X : return(this.X() >obj.X() ? 1 : this.X() <obj.X() ? -1 : 0); case ELEMENT_SORT_BY_Y : return(this.Y() >obj.Y() ? 1 : this.Y() <obj.Y() ? -1 : 0); case ELEMENT_SORT_BY_WIDTH : return(this.Width() >obj.Width() ? 1 : this.Width() <obj.Width() ? -1 : 0); case ELEMENT_SORT_BY_HEIGHT : return(this.Height() >obj.Height() ? 1 : this.Height() <obj.Height() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
Now the sorting will be based on all available properties of the object.
Object List Class
In the article in this series about tables “Implementation of a table model in MQL5: Applying the MVC concept" we have already discussed the linked list class. Simply transfer this class to Controls.mqh file:
//+------------------------------------------------------------------+ //| Класс связанного списка объектов | //+------------------------------------------------------------------+ class CListObj : public CList { protected: ENUM_ELEMENT_TYPE m_element_type; // Тип создаваемого объекта в CreateElement() public: //--- Установка типа элемента void SetElementType(const ENUM_ELEMENT_TYPE type) { this.m_element_type=type; } //--- Виртуальный метод (1) загрузки списка из файла, (2) создания элемента списка virtual bool Load(const int file_handle); virtual CObject *CreateElement(void); }; //+------------------------------------------------------------------+ //| Загрузка списка из файла | //+------------------------------------------------------------------+ bool CListObj::Load(const int file_handle) { //--- Переменные CObject *node; bool result=true; //--- Проверяем хэндл if(file_handle==INVALID_HANDLE) return(false); //--- Загрузка и проверка маркера начала списка - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return(false); //--- Загрузка и проверка типа списка if(::FileReadInteger(file_handle,INT_VALUE)!=this.Type()) return(false); //--- Чтение размера списка (количество объектов) uint num=::FileReadInteger(file_handle,INT_VALUE); //--- Последовательно заново создаём элементы списка с помощью вызова метода Load() объектов node this.Clear(); for(uint i=0; i<num; i++) { //--- Читаем и проверяем маркер начала данных объекта - 0xFFFFFFFFFFFFFFFF if(::FileReadLong(file_handle)!=MARKER_START_DATA) return false; //--- Читаем тип объекта this.m_element_type=(ENUM_ELEMENT_TYPE)::FileReadInteger(file_handle,INT_VALUE); node=this.CreateElement(); if(node==NULL) return false; this.Add(node); //--- Сейчас файловый указатель смещён относительно начала маркера объекта на 12 байт (8 - маркер, 4 - тип) //--- Поставим указатель на начало данных объекта и загрузим свойства объекта из файла методом Load() элемента node. if(!::FileSeek(file_handle,-12,SEEK_CUR)) return false; result &=node.Load(file_handle); } //--- Результат return result; } //+------------------------------------------------------------------+ //| Метод создания элемента списка | //+------------------------------------------------------------------+ CObject *CListObj::CreateElement(void) { //--- В зависимости от типа объекта в m_element_type, создаём новый объект switch(this.m_element_type) { case ELEMENT_TYPE_BASE : return new CBaseObj(); // Базовый объект графических элементов case ELEMENT_TYPE_COLOR : return new CColor(); // Объект цвета case ELEMENT_TYPE_COLORS_ELEMENT : return new CColorElement(); // Объект цветов элемента графического объекта case ELEMENT_TYPE_RECTANGLE_AREA : return new CBound(); // Прямоугольная область элемента case ELEMENT_TYPE_IMAGE_PAINTER : return new CImagePainter(); // Объект для рисования изображений case ELEMENT_TYPE_CANVAS_BASE : return new CCanvasBase(); // Базовый объект холста графических элементов case ELEMENT_TYPE_ELEMENT_BASE : return new CElementBase(); // Базовый объект графических элементов case ELEMENT_TYPE_LABEL : return new CLabel(); // Текстовая метка case ELEMENT_TYPE_BUTTON : return new CButton(); // Простая кнопка case ELEMENT_TYPE_BUTTON_TRIGGERED : return new CButtonTriggered(); // Двухпозиционная кнопка case ELEMENT_TYPE_BUTTON_ARROW_UP : return new CButtonArrowUp(); // Кнопка со стрелкой вверх case ELEMENT_TYPE_BUTTON_ARROW_DOWN : return new CButtonArrowDown(); // Кнопка со стрелкой вниз case ELEMENT_TYPE_BUTTON_ARROW_LEFT : return new CButtonArrowLeft(); // Кнопка со стрелкой влево case ELEMENT_TYPE_BUTTON_ARROW_RIGHT: return new CButtonArrowRight(); // Кнопка со стрелкой вправо case ELEMENT_TYPE_CHECKBOX : return new CCheckBox(); // Элемент управления CheckBox case ELEMENT_TYPE_RADIOBUTTON : return new CRadioButton(); // Элемент управления RadioButton case ELEMENT_TYPE_PANEL : return new CPanel(); // Элемент управления Panel case ELEMENT_TYPE_GROUPBOX : return new CGroupBox(); // Элемент управления GroupBox case ELEMENT_TYPE_CONTAINER : return new CContainer(); // Элемент управления GroupBox default : return NULL; } }
In objects of this class, we will store UI elements that are linked to the parent element, and if necessary, we will implement lists of various objects as part of classes of graphical elements. The class will also be required in the methods for loading element properties from files.
Base Class of a Graphical Element
All graphical elements have properties inherent to each of the entire list of elements. To manage these properties, save them to a file, and load them from a file, put them in a separate class, from which all graphic elements will inherit. This will simplify their further development.
Implement a new base class of a araphical element:
//+------------------------------------------------------------------+ //| Базовый класс графического элемента | //+------------------------------------------------------------------+ class CElementBase : public CCanvasBase { protected: CImagePainter m_painter; // Класс рисования int m_group; // Группа элементов public: //--- Возвращает указатель на класс рисования CImagePainter *Painter(void) { return &this.m_painter; } //--- (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(); } //--- (1) Устанавливает, (2) возвращает группу элементов virtual void SetGroup(const int group) { this.m_group=group; } int Group(void) const { return this.m_group; } //--- Возвращает описание объекта 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_ELEMENT_BASE);} //--- Конструкторы/деструктор CElementBase(void) {} CElementBase(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CElementBase(void) {} };
Parametric constructor:
//+----------------------------------------------------------------------+ //| CElementBase::Конструктор параметрический. Строит элемент в указанном| //| окне указанного графика с указанными текстом, координами и размерами | //+----------------------------------------------------------------------+ CElementBase::CElementBase(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CCanvasBase(object_name,chart_id,wnd,x,y,w,h),m_group(-1) { //--- Объекту рисования назначаем канвас переднего плана и //--- обнуляем координаты и размеры, что делает его неактивным this.m_painter.CanvasAssign(this.GetForeground()); this.m_painter.SetXY(0,0); this.m_painter.SetSize(0,0); }
In the initialization list, values of formal constructor parameters are passed to the constructor of the parent class. And then a canvas is assigned for drawing images. Its size is reset. Where it is necessary to use a drawing object, set its coordinates and dimensions. The zero size of the drawing area makes it inactive.
A method for comparing two objects:
//+------------------------------------------------------------------+ //| CElementBase::Сравнение двух объектов | //+------------------------------------------------------------------+ int CElementBase::Compare(const CObject *node,const int mode=0) const { if(node==NULL) return -1; const CElementBase *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_X : return(this.X() >obj.X() ? 1 : this.X() <obj.X() ? -1 : 0); case ELEMENT_SORT_BY_Y : return(this.Y() >obj.Y() ? 1 : this.Y() <obj.Y() ? -1 : 0); case ELEMENT_SORT_BY_WIDTH : return(this.Width() >obj.Width() ? 1 : this.Width() <obj.Width() ? -1 : 0); case ELEMENT_SORT_BY_HEIGHT : return(this.Height() >obj.Height() ? 1 : this.Height() <obj.Height() ? -1 : 0); case ELEMENT_SORT_BY_COLOR_BG : return(this.BackColor() >obj.BackColor() ? 1 : this.BackColor() <obj.BackColor() ? -1 : 0); case ELEMENT_SORT_BY_COLOR_FG : return(this.ForeColor() >obj.ForeColor() ? 1 : this.ForeColor() <obj.ForeColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA_BG : return(this.AlphaBG() >obj.AlphaBG() ? 1 : this.AlphaBG() <obj.AlphaBG() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA_FG : return(this.AlphaFG() >obj.AlphaFG() ? 1 : this.AlphaFG() <obj.AlphaFG() ? -1 : 0); case ELEMENT_SORT_BY_STATE : return(this.State() >obj.State() ? 1 : this.State() <obj.State() ? -1 : 0); case ELEMENT_SORT_BY_GROUP : return(this.Group() >obj.Group() ? 1 : this.Group() <obj.Group() ? -1 : 0); case ELEMENT_SORT_BY_ZORDER : return(this.ObjectZOrder() >obj.ObjectZOrder() ? 1 : this.ObjectZOrder() <obj.ObjectZOrder() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
The method compares two objects by all available properties.
A Method That Returns Description of the Object:
//+------------------------------------------------------------------+ //| CElementBase::Возвращает описание объекта | //+------------------------------------------------------------------+ string CElementBase::Description(void) { string nm=this.Name(); string name=(nm!="" ? ::StringFormat(" \"%s\"",nm) : nm); string area=::StringFormat("x %d, y %d, w %d, h %d",this.X(),this.Y(),this.Width(),this.Height()); return ::StringFormat("%s%s (%s, %s): ID %d, Group %d, %s",ElementDescription((ENUM_ELEMENT_TYPE)this.Type()),name,this.NameBG(),this.NameFG(),this.ID(),this.Group(),area); }
The method creates and returns a string from some object properties that are convenient for debugging, for example:
Container "Main" (ContainerBG, ContainerFG): ID 1, Group -1, x 100, y 40, w 300, h 200
A container object with the user name "Main", with the names of canvas background ContainerBG and foreground ContainerFG; object ID 1, group -1 (not assigned), coordinate x 100, y 40, width 300, height 200.
A Method for Operating Files:
//+------------------------------------------------------------------+ //| CElementBase::Сохранение в файл | //+------------------------------------------------------------------+ bool CElementBase::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CCanvasBase::Save(file_handle)) return false; //--- Сохраняем объект изображения if(!this.m_painter.Save(file_handle)) return false; //--- Сохраняем группу if(::FileWriteInteger(file_handle,this.m_group,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CElementBase::Загрузка из файла | //+------------------------------------------------------------------+ bool CElementBase::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CCanvasBase::Load(file_handle)) return false; //--- Загружаем объект изображения if(!this.m_painter.Load(file_handle)) return false; //--- Загружаем группу this.m_group=::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
Refining Simple Controls
Since some properties have now been moved to the new base object of graphical elements, remove them from the class of text label object:
//+------------------------------------------------------------------+ //| Класс текстовой метки | //+------------------------------------------------------------------+ 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) {} };
And to avoid prescribing parameter settings in each constructor and in each object, put them in separate methods:
//+------------------------------------------------------------------+ //| Класс текстовой метки | //+------------------------------------------------------------------+ class CLabel : public CElementBase { protected: 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: //--- (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.ClearText(); this.m_text_x=x; } void SetTextShiftV(const int y) { this.ClearText(); this.m_text_y=y; } //--- Выводит текст 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); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(const string text); virtual void InitColors(void){} //--- Конструкторы/деструктор 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 string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CLabel(void) {} };
Now, instead of using this type of all object constructors (as it was before)
//+------------------------------------------------------------------+ //| 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); }
They all will look as follows:
//+------------------------------------------------------------------+ //| CLabel::Конструктор по умолчанию. Строит метку в главном окне | //| текущего графика в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CLabel::CLabel(void) : CElementBase("Label","Label",::ChartID(),0,0,0,DEF_LABEL_W,DEF_LABEL_H), m_text_x(0), m_text_y(0) { //--- Инициализация this.Init("Label"); } //+------------------------------------------------------------------+ //| CLabel::Конструктор параметрический. Строит метку в главном окне | //| текущего графика с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CLabel::CLabel(const string object_name, const string text,const int x,const int y,const int w,const int h) : CElementBase(object_name,text,::ChartID(),0,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Инициализация this.Init(text); } //+-------------------------------------------------------------------+ //| 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) : CElementBase(object_name,text,::ChartID(),wnd,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Инициализация this.Init(text); } //+-------------------------------------------------------------------+ //| CLabel::Конструктор параметрический. Строит метку в указанном окне| //| указанного графика с указанными текстом, координами и размерами | //+-------------------------------------------------------------------+ CLabel::CLabel(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CElementBase(object_name,text,chart_id,wnd,x,y,w,h), m_text_x(0), m_text_y(0) { //--- Инициализация this.Init(text); }
Implement the initialization method:
//+------------------------------------------------------------------+ //| CLabel::Инициализация | //+------------------------------------------------------------------+ void CLabel::Init(const string text) { //--- Устанавливаем текущий и предыдущий текст this.SetText(text); this.SetTextPrev(""); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); }
Now, for classes inherited from the CLabel class, it is possible to assign a method for setting default colors using a virtual method InitColors().
The number of properties to compare in the comparison method is now maximized. You can compare by all available properties of a graphical element + label text:
//+------------------------------------------------------------------+ //| CLabel::Сравнение двух объектов | //+------------------------------------------------------------------+ int CLabel::Compare(const CObject *node,const int mode=0) const { if(node==NULL) return -1; 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_X : return(this.X() >obj.X() ? 1 : this.X() <obj.X() ? -1 : 0); case ELEMENT_SORT_BY_Y : return(this.Y() >obj.Y() ? 1 : this.Y() <obj.Y() ? -1 : 0); case ELEMENT_SORT_BY_WIDTH : return(this.Width() >obj.Width() ? 1 : this.Width() <obj.Width() ? -1 : 0); case ELEMENT_SORT_BY_HEIGHT : return(this.Height() >obj.Height() ? 1 : this.Height() <obj.Height() ? -1 : 0); case ELEMENT_SORT_BY_COLOR_BG : return(this.BackColor() >obj.BackColor() ? 1 : this.BackColor() <obj.BackColor() ? -1 : 0); case ELEMENT_SORT_BY_COLOR_FG : return(this.ForeColor() >obj.ForeColor() ? 1 : this.ForeColor() <obj.ForeColor() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA_BG : return(this.AlphaBG() >obj.AlphaBG() ? 1 : this.AlphaBG() <obj.AlphaBG() ? -1 : 0); case ELEMENT_SORT_BY_ALPHA_FG : return(this.AlphaFG() >obj.AlphaFG() ? 1 : this.AlphaFG() <obj.AlphaFG() ? -1 : 0); case ELEMENT_SORT_BY_STATE : return(this.State() >obj.State() ? 1 : this.State() <obj.State() ? -1 : 0); case ELEMENT_SORT_BY_ZORDER : return(this.ObjectZOrder() >obj.ObjectZOrder() ? 1 : this.ObjectZOrder() <obj.ObjectZOrder() ? -1 : 0); default : return(this.ID() >obj.ID() ? 1 : this.ID() <obj.ID() ? -1 : 0); } }
In the methods for working with files, we now refer to a method of parent class not of CCanvasBase, but to a new one — CElementBase:
//+------------------------------------------------------------------+ //| CLabel::Сохранение в файл | //+------------------------------------------------------------------+ bool CLabel::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CElementBase::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(!CElementBase::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; }
In the simple button class declare the same two initialization methods and a timer event handler:
//+------------------------------------------------------------------+ //| Класс простой кнопки | //+------------------------------------------------------------------+ 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); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(const string text); virtual void InitColors(void){} //--- Обработчик события таймера virtual void TimerEventHandler(void); //--- Конструкторы/деструктор 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 string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButton (void) {} };
The timer event handler the counters of the auto-repeat event class will run.
In class constructors, just like in the text label class, simply call the class initialization method:
//+------------------------------------------------------------------+ //| CButton::Конструктор по умолчанию. Строит кнопку в главном окне | //| текущего графика в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButton::CButton(void) : CLabel("Button","Button",::ChartID(),0,0,0,DEF_BUTTON_W,DEF_BUTTON_H) { //--- Инициализация this.Init(""); } //+-------------------------------------------------------------------+ //| 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,text,::ChartID(),0,x,y,w,h) { //--- Инициализация this.Init(""); } //+---------------------------------------------------------------------+ //| 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,text,::ChartID(),wnd,x,y,w,h) { //--- Инициализация this.Init(""); } //+---------------------------------------------------------------------+ //| CButton::Конструктор параметрический. Строит кнопку в указанном окне| //| указанного графика с указанными текстом, координами и размерами | //+---------------------------------------------------------------------+ CButton::CButton(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CLabel(object_name,text,chart_id,wnd,x,y,w,h) { //--- Инициализация this.Init(""); }
Since all buttons of different types are inherited from this simple button class, it is sufficient to set the auto-repeat event flag and initialize an object of the auto-repeat event class. And then the button will be vested with this functionality. Here, for a simple button, the auto-repeat event flag is reset in the initialization method:
//+------------------------------------------------------------------+ //| CButton::Инициализация | //+------------------------------------------------------------------+ void CButton::Init(const string text) { //--- Устанавливаем состояние по умолчанию this.SetState(ELEMENT_STATE_DEF); //--- Фон и передний план - непрозрачные this.SetAlpha(255); //--- Смещение текста от левого края кнопки по умолчанию this.m_text_x=2; //--- Автоповтор нажатий отключен this.m_autorepeat_flag=false; }
The method for comparing two objects returns the result of calling the similar method of the parent class:
//+------------------------------------------------------------------+ //| CButton::Сравнение двух объектов | //+------------------------------------------------------------------+ int CButton::Compare(const CObject *node,const int mode=0) const { return CLabel::Compare(node,mode); }
In fact, you can simply remove the declaration and implementation of this virtual method from this class. And it will work exactly the same way — the method of the parent class will be called. But let's leave it that way for now, since the library is still under development, and it may be necessary to refine this method here. As a result, at the end of development, the need for this method will be visible here (and in subsequent classes of simple elements that will be refined).
In the timer event handler, the main method of the event auto-repeat class is triggered if the event auto-repeat flag is set for the class:
//+------------------------------------------------------------------+ //| Обработчик события таймера | //+------------------------------------------------------------------+ void CButton::TimerEventHandler(void) { if(this.m_autorepeat_flag) this.m_autorepeat.Process(); }
Changes to the two-position button class:
//+------------------------------------------------------------------+ //| Класс двухпозиционной кнопки | //+------------------------------------------------------------------+ class CButtonTriggered : 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_TRIGGERED); } //--- Обработчик событий нажатий кнопок мышки (Press) virtual void OnPressEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(const string text); 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 string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonTriggered (void) {} }; //+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(void) : CButton("Button","Button",::ChartID(),0,0,0,DEF_BUTTON_W,DEF_BUTTON_H) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| 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,text,::ChartID(),0,x,y,w,h) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| 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,text,::ChartID(),wnd,x,y,w,h) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| CButtonTriggered::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonTriggered::CButtonTriggered(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,text,chart_id,wnd,x,y,w,h) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| CButtonTriggered::Инициализация | //+------------------------------------------------------------------+ void CButtonTriggered::Init(const string text) { //--- Инициализируем цвета по умолчанию this.InitColors(); }
In general, everything here is simply brought to a common standard for classes of simple elements: call the Init() method in the class constructor, and there prescribe necessary steps for initializing the class. This has now been done for all classes of simple UI elements.
In the arrow button classes, in their initialization methods, it is necessary to set flags for using event auto-repeat and set parameters of the object of the event auto-repeat class.
See how this is done using an example of the up arrow button 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);} //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(const string text); virtual void InitColors(void){} //--- Конструкторы/деструктор CButtonArrowUp(void); CButtonArrowUp(const string object_name, const string text, const int x, const int y, const int w, const int h); CButtonArrowUp(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CButtonArrowUp(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CButtonArrowUp (void) {} }; //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор по умолчанию. | //| Строит кнопку в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(void) : CButton("Arrow Up Button","",::ChartID(),0,0,0,DEF_BUTTON_W,DEF_BUTTON_H) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name, const string text,const int x,const int y,const int w,const int h) : CButton(object_name,text,::ChartID(),0,x,y,w,h) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(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,text,::ChartID(),wnd,x,y,w,h) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Конструктор параметрический. | //| Строит кнопку в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CButtonArrowUp::CButtonArrowUp(const string object_name, const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,text,chart_id,wnd,x,y,w,h) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| CButtonArrowUp::Инициализация | //+------------------------------------------------------------------+ void CButtonArrowUp::Init(const string text) { //--- Инициализируем цвета по умолчанию this.InitColors(); //--- Устанавливаем смещение и размеры области изображенеия this.SetImageBound(1,1,this.Height()-2,this.Height()-2); //--- Инициализируем счётчики автоповтора this.m_autorepeat_flag=true; //--- Инициализируем свойства объекта управления автоповтором событий this.m_autorepeat.SetChartID(this.m_chart_id); this.m_autorepeat.SetID(0); this.m_autorepeat.SetName("ButtUpAutorepeatControl"); this.m_autorepeat.SetDelay(DEF_AUTOREPEAT_DELAY); this.m_autorepeat.SetInterval(DEF_AUTOREPEAT_INTERVAL); this.m_autorepeat.SetEvent(CHARTEVENT_OBJECT_CLICK,0,0,this.NameFG()); }
The same thing is done in the classes of down, left, and right arrow buttons.
Container Classes for Placing Controls
All previously created elements are simple controls. They are quite functional and have customizable behavior for user interaction. But..., these are simple controls. Now it is necessary to develop container elements that allow you to attach other graphical components to themselves and provide joint management of a linked group of objects. And the very first element from the list of containers is the "Panel".
“Panel” Class
The Panel graphical element is a base container element of user interface. It is designed to group up and organize other graphical elements in the general concept of program’s graphical interface. The panel serves as the basis for building complex elements: buttons, labels, input fields, and other controls are placed on it. Using the panel, you can structure the visual space, create logical blocks, groups of settings, and any other component elements of the interface. The panel not only visually combines linked elements, but also controls their position, visibility, locking, event handling, and stateful behavior.
The panel class will enable to locate many different controls on it. All elements that go beyond panel boundaries will be cropped at its edges. All manipulations being performed programmatically with the panel will also affect all the controls included in the panel — hiding, displaying, moving, etc.
Continue writing the code in Controls.mqh file:
//+------------------------------------------------------------------+ //| Класс панели | //+------------------------------------------------------------------+ class CPanel : public CLabel { private: CElementBase m_temp_elm; // Временный объект для поиска элементов CBound m_temp_bound; // Временный объект для поиска областей protected: CListObj m_list_elm; // Список прикреплённых элементов CListObj m_list_bounds; // Список областей //--- Добавляет новый элемент в список bool AddNewElement(CElementBase *element); public: //--- Возвращает указатель на список (1) прикреплённых элементов, (2) областей CListObj *GetListAttachedElements(void) { return &this.m_list_elm; } CListObj *GetListBounds(void) { return &this.m_list_bounds; } //--- Возвращает элемент по (1) индексу в списке, (2) идентификатору, (3) назначенному имени объекта CElementBase *GetAttachedElementAt(const uint index) { return this.m_list_elm.GetNodeAtIndex(index); } CElementBase *GetAttachedElementByID(const int id); CElementBase *GetAttachedElementByName(const string name); //--- Возвращает область по (1) индексу в списке, (2) идентификатору, (3) назначенному имени области CBound *GetBoundAt(const uint index) { return this.m_list_bounds.GetNodeAtIndex(index); } CBound *GetBoundByID(const int id); CBound *GetBoundByName(const string name); //--- Создаёт и добавляет (1) новый, (2) ранее созданный элемент в список virtual CElementBase *InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h); virtual CElementBase *InsertElement(CElementBase *element,const int dx,const int dy); //--- Создаёт и добавляет в список новую область CBound *InsertNewBound(const string name,const int dx,const int dy,const int w,const int h); //--- Рисует внешний вид 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_PANEL); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Устанавливает объекту новые координаты XY virtual bool Move(const int x,const int y); //--- Смещает объект по осям XY на указанное смещение virtual bool Shift(const int dx,const int dy); //--- (1) Скрывает (2) отображает объект на всех периодах графика, //--- (3) помещает объект на передний план, (4) блокирует, (5) разблокирует элемент, virtual void Hide(const bool chart_redraw); virtual void Show(const bool chart_redraw); virtual void BringToTop(const bool chart_redraw); virtual void Block(const bool chart_redraw); virtual void Unblock(const bool chart_redraw); //--- Выводит в журнал описание объекта virtual void Print(void); //--- Распечатывает список (1) присоединённых объектов, (2) областей void PrintAttached(const uint tab=3); void PrintBounds(void); //--- Обработчик событий virtual void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam); //--- Обработчик события таймера virtual void TimerEventHandler(void); //--- Конструкторы/деструктор CPanel(void); CPanel(const string object_name, const string text, const int x, const int y, const int w, const int h); CPanel(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CPanel(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CPanel (void) { this.m_list_elm.Clear(); this.m_list_bounds.Clear(); } };
In constructors, in the initialization string, all formal parameters are passed to the parent class, and then the object initialization method is called:
//+------------------------------------------------------------------+ //| CPanel::Инициализация | //+------------------------------------------------------------------+ void CPanel::Init(void) { //--- Инициализация цветов по умолчанию this.InitColors(); //--- Фон - прозрачный, передний план - нет this.SetAlphaBG(0); this.SetAlphaFG(255); //--- Устанавливаем смещение и размеры области изображенеия this.SetImageBound(0,0,this.Width(),this.Height()); //--- Ширина рамки this.SetBorderWidth(2); }
Default Object Color Initialization Method:
//+------------------------------------------------------------------+ //| CPanel::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CPanel::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.InitBackColorsAct(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack,clrBlack,clrBlack,clrSilver); this.InitForeColorsAct(clrBlack,clrBlack,clrBlack,clrSilver); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrNULL,clrNULL,clrNULL,clrNULL); this.InitBorderColorsAct(clrNULL,clrNULL,clrNULL,clrNULL); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrNULL); this.InitForeColorBlocked(clrSilver); }
In the method for comparing two objects the result of running the similar method of the parent class is returned:
//+------------------------------------------------------------------+ //| CPanel::Сравнение двух объектов | //+------------------------------------------------------------------+ int CPanel::Compare(const CObject *node,const int mode=0) const { return CLabel::Compare(node,mode); }
The Method of Drawing the Panel Appearance:
//+------------------------------------------------------------------+ //| CPanel::Рисует внешний вид | //+------------------------------------------------------------------+ void CPanel::Draw(const bool chart_redraw) { //--- Заливаем кнопку цветом фона this.Fill(this.BackColor(),false); //--- Очищаем область рисунка this.m_painter.Clear(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),false); //--- Задаём цвет для тёмной и светлой линий и рисуем рамку панели color clr_dark =(this.BackColor()==clrNULL ? this.BackColor() : this.GetBackColorControl().NewColor(this.BackColor(),-20,-20,-20)); color clr_light=(this.BackColor()==clrNULL ? this.BackColor() : this.GetBackColorControl().NewColor(this.BackColor(), 6, 6, 6)); this.m_painter.FrameGroupElements(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()), this.m_painter.Width(),this.m_painter.Height(),this.Text(), this.ForeColor(),clr_dark,clr_light,this.AlphaFG(),true); //--- Обновляем канвас фона без перерисовки графика this.m_background.Update(false); //--- Рисуем элементы списка for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.Draw(false); } //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
First, the panel is drawn, and then all the controls attached to it are looped.
A Method That Adds a New Element to the List:
//+------------------------------------------------------------------+ //| CPanel::Добавляет новый элемент в список | //+------------------------------------------------------------------+ bool CPanel::AddNewElement(CElementBase *element) { //--- Если передан пустой указатель - сообщаем об этом и возвращаем false if(element==NULL) { ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__); return false; } //--- Устанавливаем списку флаг сортировки по идентификатору this.m_list_elm.Sort(ELEMENT_SORT_BY_ID); //--- Если такого элемента нет в списке - возвращаем результат его добавления в список if(this.m_list_elm.Search(element)==NULL) return(this.m_list_elm.Add(element)>-1); //--- Элемент с таким идентификатором уже есть в списке - возвращаем false return false; }
A pointer to the element to be placed in the list is passed to the method. If the item with the ID assigned to it is not yet in the list, return the result of adding the element to the list. Otherwise, return false.
A Method That Implements and Adds a New Element to the List:
//+------------------------------------------------------------------+ //| CPanel::Создаёт и добавляет новый элемент в список | //+------------------------------------------------------------------+ CElementBase *CPanel::InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h) { //--- Создаём имя графического объекта int elm_total=this.m_list_elm.Total(); string obj_name=this.NameFG()+"_"+ElementShortName(type)+(string)elm_total; //--- Рассчитываем координаты int x=this.X()+dx; int y=this.Y()+dy; //--- В зависимости от типа объекта, создаём новый объект CElementBase *element=NULL; switch(type) { case ELEMENT_TYPE_LABEL : element = new CLabel(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Текстовая метка case ELEMENT_TYPE_BUTTON : element = new CButton(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Простая кнопка case ELEMENT_TYPE_BUTTON_TRIGGERED : element = new CButtonTriggered(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Двухпозиционная кнопка case ELEMENT_TYPE_BUTTON_ARROW_UP : element = new CButtonArrowUp(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Кнопка со стрелкой вверх case ELEMENT_TYPE_BUTTON_ARROW_DOWN : element = new CButtonArrowDown(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Кнопка со стрелкой вниз case ELEMENT_TYPE_BUTTON_ARROW_LEFT : element = new CButtonArrowLeft(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Кнопка со стрелкой влево case ELEMENT_TYPE_BUTTON_ARROW_RIGHT: element = new CButtonArrowRight(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Кнопка со стрелкой вправо case ELEMENT_TYPE_CHECKBOX : element = new CCheckBox(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления CheckBox case ELEMENT_TYPE_RADIOBUTTON : element = new CRadioButton(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления RadioButton case ELEMENT_TYPE_PANEL : element = new CPanel(obj_name,"",this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления Panel case ELEMENT_TYPE_GROUPBOX : element = new CGroupBox(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления GroupBox case ELEMENT_TYPE_SCROLLBAR_THUMB_H : element = new CScrollBarThumbH(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Полоса прокрутки горизонтального ScrollBar case ELEMENT_TYPE_SCROLLBAR_THUMB_V : element = new CScrollBarThumbV(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Полоса прокрутки вертикального ScrollBar case ELEMENT_TYPE_SCROLLBAR_H : element = new CScrollBarH(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления горизонтальный ScrollBar case ELEMENT_TYPE_SCROLLBAR_V : element = new CScrollBarV(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления вертикальный ScrollBar case ELEMENT_TYPE_CONTAINER : element = new CContainer(obj_name,text,this.m_chart_id,this.m_wnd,x,y,w,h); break; // Элемент управления Container default : element = NULL; } //--- Если новый элемент не создан - сообщаем об этом и возвращаем NULL if(element==NULL) { ::PrintFormat("%s: Error. Failed to create graphic element %s",__FUNCTION__,ElementDescription(type)); return NULL; } //--- Устанавливаем идентификатор, имя, контейнер и z-order элемента element.SetID(elm_total); element.SetName(user_name); element.SetContainerObj(&this); element.ObjectSetZOrder(this.ObjectZOrder()+1); //--- Если созданный элемент не добавлен в список - сообщаем об этом, удаляем созданный элемент и возвращаем NULL if(!this.AddNewElement(element)) { ::PrintFormat("%s: Error. Failed to add %s element with ID %d to list",__FUNCTION__,ElementDescription(type),element.ID()); delete element; return NULL; } //--- Получаем родительский элемент, к которому привязаны дочерние CElementBase *elm=this.GetContainer(); //--- Если родительский элемент имеет тип "Контейнер", значит, у него есть полосы прокрутки if(elm!=NULL && elm.Type()==ELEMENT_TYPE_CONTAINER) { //--- Преобразуем CElementBase в CContainer CContainer *container_obj=elm; //--- Если горизонтальная полоса прокрутки видима, if(container_obj.ScrollBarHorIsVisible()) { //--- получаем указатель на горизонтальный скроллбар и переносим его на передний план CScrollBarH *sbh=container_obj.GetScrollBarH(); if(sbh!=NULL) sbh.BringToTop(false); } //--- Если вертикальная полоса прокрутки видима, if(container_obj.ScrollBarVerIsVisible()) { //--- получаем указатель на вертикальный скроллбар и переносим его на передний план CScrollBarV *sbv=container_obj.GetScrollBarV(); if(sbv!=NULL) sbv.BringToTop(false); } } //--- Возвращаем указатель на созданный и присоединённый элемент return element; }
In comments to the method, all its logic is described in detail. Let me note that when creating new classes of new controls, here we will record new types of elements to create them.
A Method That Adds a Specified Element to the List:
//+------------------------------------------------------------------+ //| CPanel::Добавляет указанный элемент в список | //+------------------------------------------------------------------+ CElementBase *CPanel::InsertElement(CElementBase *element,const int dx,const int dy) { //--- Если передан пустой или невалидный указатель на элемент - возвращаем NULL if(::CheckPointer(element)==POINTER_INVALID) { ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__); return NULL; } //--- Если передан базовый элемент - возвращаем NULL if(element.Type()==ELEMENT_TYPE_BASE) { ::PrintFormat("%s: Error. The base element cannot be used",__FUNCTION__); return NULL; } //--- Запоминаем идентификатор элемента и устанавливаем новый int id=element.ID(); element.SetID(this.m_list_elm.Total()); //--- Добавляем элемент в список; при неудаче - сообщаем об этом, устанавливаем начальный идентификатор и возвращаем NULL if(!this.AddNewElement(element)) { ::PrintFormat("%s: Error. Failed to add element %s to list",__FUNCTION__,ElementDescription((ENUM_ELEMENT_TYPE)element.Type())); element.SetID(id); return NULL; } //--- Устанавливаем новые координаты, контейнер и z-order элемента int x=this.X()+dx; int y=this.Y()+dy; element.Move(x,y); element.SetContainerObj(&this); element.ObjectSetZOrder(this.ObjectZOrder()+1); //--- Возвращаем указатель на присоединённый элемент return element; }
The method, unlike the previous one, does not create a new element, but adds an existing one to the list, a pointer to which is passed to the method. If the element was deleted, or the pointer to the element is NULL, the method returns. If an element still cannot be placed in the list, its identifier which was set to it before attempting to join to the list is returned to it. If an element is added to the list, it is provided with new coordinates specified in the formal method parameters, as well a container and a z-order value are assigned to it.
A Method That Returns an Element by ID:
//+------------------------------------------------------------------+ //| CPanel::Возвращает элемент по идентификатору | //+------------------------------------------------------------------+ CElementBase *CPanel::GetAttachedElementByID(const int id) { for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL && elm.ID()==id) return elm; } return NULL; }
In a loop through all the linked elements, search for an element with the specified identifier. If found, return the pointer to the element in the list. If not found, return NULL.
A Method That Returns an Element by Assigned Object Name:
//+------------------------------------------------------------------+ //| CPanel::Возвращает элемент по назначенному имени объекта | //+------------------------------------------------------------------+ CElementBase *CPanel::GetAttachedElementByName(const string name) { for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL && elm.Name()==name) return elm; } return NULL; }
In a loop through all the linked elements, search for an element with the specified user name. If found, return the pointer to the element in the list. If not found, return NULL. The method offers convenient search by the name assigned to the graphical element. It is more convenient to name an element and refer to it by its unique name than to remember impersonal identifiers to refer to the element.
A Method That Implements and Adds a New Area to the List:
//+------------------------------------------------------------------+ //| Создаёт и добавляет в список новую область | //+------------------------------------------------------------------+ CBound *CPanel::InsertNewBound(const string name,const int dx,const int dy,const int w,const int h) { //--- Проверяем есть ли в списке область с указанным именем и, если да - сообщаем об этом и возвращаем NULL this.m_temp_bound.SetName(name); if(this.m_list_bounds.Search(&this.m_temp_bound)!=NULL) { ::PrintFormat("%s: Error. An area named \"%s\" is already in the list",__FUNCTION__,name); return NULL; } //--- Создаём новый объект-область; при неудаче - сообщаем об этом и возвращаем NULL CBound *bound=new CBound(dx,dy,w,h); if(bound==NULL) { ::PrintFormat("%s: Error. Failed to create CBound object",__FUNCTION__); return NULL; } //--- Если новый объект не удалось добавить в список - сообщаем об этом, удаляем объект и возвращаем NULL if(this.m_list_bounds.Add(bound)==-1) { ::PrintFormat("%s: Error. Failed to add CBound object to list",__FUNCTION__); delete bound; return NULL; } //--- Устанавливаем имя области и идентификатор, и возвращаем указатель на объект bound.SetName(name); bound.SetID(this.m_list_bounds.Total()); return bound; }
Several independent areas can be marked up on the panel. They can be controlled separately. What should be placed in each individual area is up to the programmer to decide, but separate areas add flexibility in planning and building graphical interfaces. All areas are stored in a list, and the above method creates a new area object and adds it to the list, assigning it a name being passed in formal parameters of the method and an identifier which depends on the total number of areas in the list.
A Method That Outputs Object Description to the Log:
//+------------------------------------------------------------------+ //| Выводит в журнал описание объекта | //+------------------------------------------------------------------+ void CPanel::Print(void) { CBaseObj::Print(); this.PrintAttached(); }
Print out description of the object and all the associated elements in the log.
A Method That Prints Out a List of Attached Objects:
//+------------------------------------------------------------------+ //| CPanel::Распечатывает список присоединённых объектов | //+------------------------------------------------------------------+ void CPanel::PrintAttached(const uint tab=3) { //--- В цикле по всем привязанным элементам int total=this.m_list_elm.Total(); for(int i=0;i<total;i++) { //--- получаем очередной элемент CElementBase *elm=this.GetAttachedElementAt(i); if(elm==NULL) continue; //--- Получаем тип элемента и, если это полоса прокрутки - пропускаем его ENUM_ELEMENT_TYPE type=(ENUM_ELEMENT_TYPE)elm.Type(); if(type==ELEMENT_TYPE_SCROLLBAR_H || type==ELEMENT_TYPE_SCROLLBAR_V) continue; //--- Распечатываем в журнале описание элемента ::PrintFormat("%*s[%d]: %s",tab,"",i,elm.Description()); //--- Если элемент является контейнером - распечатываем в журнал его список привязанных элементов if(type==ELEMENT_TYPE_PANEL || type==ELEMENT_TYPE_GROUPBOX || type==ELEMENT_TYPE_CONTAINER) { CPanel *obj=elm; obj.PrintAttached(tab*2); } } }
The method logs descriptions of all the elements located in the list of attached objects.
A Method That Prints Out the List of Areas to the Log:
//+------------------------------------------------------------------+ //| CPanel::Распечатывает список областей | //+------------------------------------------------------------------+ void CPanel::PrintBounds(void) { //--- В цикле по списку областей элемента int total=this.m_list_bounds.Total(); for(int i=0;i<total;i++) { //--- получаем очередную область и распечатываем её описание в журнал CBound *obj=this.GetBoundAt(i); if(obj==NULL) continue; ::PrintFormat(" [%d]: %s",i,obj.Description()); }
A Method That Sets New X and Y Coordinates For an Object:
//+------------------------------------------------------------------+ //| CPanel::Устанавливает объекту новые координаты X и Y | //+------------------------------------------------------------------+ bool CPanel::Move(const int x,const int y) { //--- Вычисляем дистанцию, на которую сместится элемент int delta_x=x-this.X(); int delta_y=y-this.Y(); //--- Перемещаем элемент на указанные координаты bool res=this.ObjectMove(x,y); if(!res) return false; this.BoundMove(x,y); this.ObjectTrim(); //--- Перемещаем все привязанные элементы на рассчитанную дистанцию int total=this.m_list_elm.Total(); for(int i=0;i<total;i++) { //--- Перемещаем привязанный элемент с учётом смещения родительского элемента CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) res &=elm.Move(elm.X()+delta_x, elm.Y()+delta_y); } //--- Возвращаем результат перемещения всех привязанных элементов return res; }
First, the distance by which the element will be shifted is calculated. Then the element is shifted to the specified coordinates. After that all the attached elements are shifted by the distance calculated at the very beginning.
A Method That Shifts an Object Along the X and Y Axes by a Specified Distance of Shifting:
//+------------------------------------------------------------------+ //| CPanel::Смещает объект по осям X и Y на указанное смещение | //+------------------------------------------------------------------+ bool CPanel::Shift(const int dx,const int dy) { //--- Смещаем элемент на указанную дистанцию bool res=this.ObjectShift(dx,dy); if(!res) return false; this.BoundShift(dx,dy); this.ObjectTrim(); //--- Смещаем все привязанные элементы int total=this.m_list_elm.Total(); for(int i=0;i<total;i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) res &=elm.Shift(dx,dy); } //--- Возвращаем результат смещения всех привязанных элементов return res; }
A Method That Hides an Object On All Chart Periods:
//+------------------------------------------------------------------+ //| CPanel::Скрывает объект на всех периодах графика | //+------------------------------------------------------------------+ void CPanel::Hide(const bool chart_redraw) { //--- Если объект уже скрыт - уходим if(this.m_hidden) return; //--- Скрываем панель CCanvasBase::Hide(false); //--- Скрываем прикреплённые объекты for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.Hide(false); } //--- Если указано - перерисовываем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
A Method That Displays an Object On All Chart Periods:
//+------------------------------------------------------------------+ //| CPanel::Отображает объект на всех периодах графика | //+------------------------------------------------------------------+ void CPanel::Show(const bool chart_redraw) { //--- Если объект уже видимый - уходим if(!this.m_hidden) return; //--- Отображаем панель CCanvasBase::Show(false); //--- Отображаем прикреплённые объекты for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.Show(false); } //--- Если указано - перерисовываем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
A Method That Puts an Object in the Foreground:
//+------------------------------------------------------------------+ //| CPanel::Помещает объект на передний план | //+------------------------------------------------------------------+ void CPanel::BringToTop(const bool chart_redraw) { //--- Помещаем панель на передний план CCanvasBase::BringToTop(false); //--- Помещаем на передний план прикреплённые объекты for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.BringToTop(false); } //--- Если указано - перерисовываем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
A Method That Blocks the Element:
//+------------------------------------------------------------------+ //| CPanel::Блокирует элемент | //+------------------------------------------------------------------+ void CPanel::Block(const bool chart_redraw) { //--- Если элемент уже заблокирован - уходим if(this.m_blocked) return; //--- Блокируем панель CCanvasBase::Block(false); //--- Блокируем прикреплённые объекты for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.Block(false); } //--- Если указано - перерисовываем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
A Method That Unblocks the Element:
//+------------------------------------------------------------------+ //| CPanel::Разблокирует элемент | //+------------------------------------------------------------------+ void CPanel::Unblock(const bool chart_redraw) { //--- Если элемент уже разблокирован - уходим if(!this.m_blocked) return; //--- Разблокируем панель CCanvasBase::Unblock(false); //--- Разблокируем прикреплённые объекты for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.Unblock(false); } //--- Если указано - перерисовываем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
A Method for Operating Files:
//+------------------------------------------------------------------+ //| CPanel::Сохранение в файл | //+------------------------------------------------------------------+ bool CPanel::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CElementBase::Save(file_handle)) return false; //--- Сохраняем список прикреплённых элементов if(!this.m_list_elm.Save(file_handle)) return false; //--- Сохраняем список областей if(!this.m_list_bounds.Save(file_handle)) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CPanel::Загрузка из файла | //+------------------------------------------------------------------+ bool CPanel::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CElementBase::Load(file_handle)) return false; //--- Загружаем список прикреплённых элементов if(!this.m_list_elm.Load(file_handle)) return false; //--- Загружаем список областей if(!this.m_list_bounds.Load(file_handle)) return false; //--- Всё успешно return true; }
Event handler:
//+------------------------------------------------------------------+ //| CPanel::Обработчик событий | //+------------------------------------------------------------------+ void CPanel::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam) { //--- Вызываем обработчик событий родительского класса CCanvasBase::OnChartEvent(id,lparam,dparam,sparam); //--- В цикле по всем привязанным элементам int total=this.m_list_elm.Total(); for(int i=0;i<total;i++) { //--- получаем очередной элемент и вызываем его обработчик событий CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.OnChartEvent(id,lparam,dparam,sparam); } }
First, the panel event handler is called, then the handlers of attached elements are called in a loop through the list of attached elements.
Timer Event Handler:
//+------------------------------------------------------------------+ //| CPanel::Обработчик события таймера | //+------------------------------------------------------------------+ void CPanel::TimerEventHandler(void) { //--- В цикле по всем привязанным элементам for(int i=0;i<this.m_list_elm.Total();i++) { //--- получаем очередной элемент и вызываем его обработчик событий таймера CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.TimerEventHandler(); } }
First, the panel timer handler is called, then the handlers of attached elements are called in a loop through the list of attached elements. If a virtual handler is implemented for any element, it will handle the timer event. At the moment, such handlers are implemented for buttons.
The next control will be a Group of Objects — GroupBox. It can be used to create "group boxes", which are often found in program interfaces: these are visual blocks with a header, inside of which there are related controls (for example, a set of radio buttons, checkboxes, buttons, input fields, etc.). Such approach helps to structure the interface, increase its readability and user friendliness. The class will be inherited from the Panel object class, which will allow you to get all the panel's features from the parent class: adding/removing elements, managing their position, event handling, saving/loading state, etc.
GroupBox Class
Continue writing the code in Controls.mqh file:
//+------------------------------------------------------------------+ //| Класс группы объектов | //+------------------------------------------------------------------+ class CGroupBox : public CPanel { public: //--- Тип объекта virtual int Type(void) const { return(ELEMENT_TYPE_GROUPBOX); } //--- Инициализация объекта класса void Init(void); //--- Устанавливает группу элементов virtual void SetGroup(const int group); //--- Создаёт и добавляет (1) новый, (2) ранее созданный элемент в список virtual CElementBase *InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h); virtual CElementBase *InsertElement(CElementBase *element,const int dx,const int dy); //--- Конструкторы/деструктор CGroupBox(void); CGroupBox(const string object_name, const string text, const int x, const int y, const int w, const int h); CGroupBox(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CGroupBox(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CGroupBox(void) {} };
A new method for setting a group has been added here, and methods for creating and adding elements to the list will be redefined.
In the class constructors, in the initialization list, all values passed in formal parameters shall be passed to the parent class constructor. Then the initialization method is called:
//+------------------------------------------------------------------+ //| CGroupBox::Конструктор по умолчанию. | //| Строит элемент в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CGroupBox::CGroupBox(void) : CPanel("GroupBox","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_PANEL_H) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CGroupBox::Конструктор параметрический. | //| Строит элемент в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CGroupBox::CGroupBox(const string object_name,const string text,const int x,const int y,const int w,const int h) : CPanel(object_name,text,::ChartID(),0,x,y,w,h) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CGroupBox::Конструктор параметрический. | //| Строит элемент в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CGroupBox::CGroupBox(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CPanel(object_name,text,::ChartID(),wnd,x,y,w,h) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CGroupBox::Конструктор параметрический. | //| Строит элемент в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CGroupBox::CGroupBox(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CPanel(object_name,text,chart_id,wnd,x,y,w,h) { //--- Инициализация this.Init(); }
An element is initialized by calling the parent class initialization method:
//+------------------------------------------------------------------+ //| CGroupBox::Инициализация | //+------------------------------------------------------------------+ void CGroupBox::Init(void) { //--- Инициализация при помощи родительского класса CPanel::Init(); }
A Method That Sets a Group of Elements:
//+------------------------------------------------------------------+ //| CGroupBox::Устанавливает группу элементов | //+------------------------------------------------------------------+ void CGroupBox::SetGroup(const int group) { //--- Устанавливаем группу этому элементу методом родительского класса CElementBase::SetGroup(group); //--- В цикле по списку привязанных элементов for(int i=0;i<this.m_list_elm.Total();i++) { //--- получаем очередной элемент и назначаем ему группу CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.SetGroup(group); } }
After setting a group for an element, a group is set for each subordinate control in a loop through the list of attached objects.
A Method That Creates and Adds a New Element to the List:
//+------------------------------------------------------------------+ //| CGroupBox::Создаёт и добавляет новый элемент в список | //+------------------------------------------------------------------+ CElementBase *CGroupBox::InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h) { //--- Создаём и добавляем в список элементов новый элемент CElementBase *element=CPanel::InsertNewElement(type,text,user_name,dx,dy,w,h); if(element==NULL) return NULL; //--- Устанавливаем созданному элементу группу, равную группе этого объекта element.SetGroup(this.Group()); return element; }
First, a new element is created and added to the list of linked objects using the parent class method. Then the newly created element is assigned a group of this object.
A Method That Adds a Specified Element to the List:
//+------------------------------------------------------------------+ //| CGroupBox::Добавляет указанный элемент в список | //+------------------------------------------------------------------+ CElementBase *CGroupBox::InsertElement(CElementBase *element,const int dx,const int dy) { //--- Добавляем в список элементов новый элемент if(CPanel::InsertElement(element,dx,dy)==NULL) return NULL; //--- Устанавливаем добавленному элементу группу, равную группе этого объекта element.SetGroup(this.Group()); return element; }
Here, similar to the previous method, but the pointer passed to the method to a previously created element is added to the list.
All elements added to CGroupBox automatically get the same group ID assigned to the panel. This allows you to implement logic when, for example, only one element from a group can be active (relevant for radio buttons), or when massive control of the state of a group of elements is required. CGroupBox displays a border with a header, separating its area from the rest of the interface. The SetGroup method allows you to assign a new identifier to the entire group, automatically setting it to all linked elements.
Next up is the Container class, which is used to create interface areas where content can extend beyond the visible area. In such cases, the user is given the option to scroll through the content using a horizontal and/or vertical scrollbar. This is especially important for implementing scrollable lists, tables, large forms, charts, and other elements that may exceed the container size.
The container class will be inherited from the panel class. But to create it, you first need to create auxiliary elements: classes of vertical and horizontal scrollbars.
A scrollbar is a user interface element designed to scroll through content that does not fit in the window visible area (container). Scrollbars allow the user to navigate horizontally and vertically, controlling the display of large lists, tables, forms, and other elements. In graphical interfaces, scrollbars provide easy navigation and make working with a large amount of information user-friendly and familiar to the user.
Classes for Creating Scrollbars
Scrollbar class is composite elements that include:
- Two arrow buttons (left/right or up/down) for step-by-step scrolling.
- A thumb which can be dragged for fast scrolling.
- A track - the area where the thumb moves.
The scrollbar thumb will be made from the Button graphical element, the track will be made from the Panel element, and there will be ready-made arrow buttons. Scroll buttons and a slider will be attached to the panel (track).
- When you click on the arrow buttons, the thumb will move by a fixed pitch, and the contents of the container will scroll by a calculated value proportional to slider's shift relative to the track.
- When dragging the thumb with the mouse or scrolling with the wheel, its position will change, and the container contents will shift proportionally.
- The scrollbar will calculate the size of the track and thumb depending on the size of the content and container visible area.
Scrollbar Thumb Classes
Continue writing the code in Controls.mqh file.
Horizontal Scrollbar Thumb Class.
//+------------------------------------------------------------------+ //| Класс ползунка горизонтальной полосы прокрутки | //+------------------------------------------------------------------+ class CScrollBarThumbH : public CButton { protected: bool m_chart_redraw; // Флаг обновления графика public: //--- (1) Устанавливает, (2) возвращает флаг обновления графика void SetChartRedrawFlag(const bool flag) { this.m_chart_redraw=flag; } bool ChartRedrawFlag(void) const { return this.m_chart_redraw; } //--- Виртуальные методы (1) сохранения в файл, (2) загрузки из файла, (3) тип объекта virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_SCROLLBAR_THUMB_H); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(const string text); //--- Обработчики событий (1) перемещения курсора, (2) прокрутки колёсика virtual void OnMoveEvent(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); //--- Конструкторы/деструктор CScrollBarThumbH(void); CScrollBarThumbH(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CScrollBarThumbH (void) {} };
The class is inherited from a simple button. Respectively, it inherits event handlers from it. A simple button does not handle the mouse wheel movement and scroll events. Therefore, these virtual methods will be implemented here. And the chart self-updating flag has been added here. What is the purpose of it? If use this class separately from the container (for example, for controls with thumbs), then when the thumb position changes, the chart must be redrawn to immediately display the changes. To do this, this flag is set to true. As part of the container, scrollbars and chart redrawing are controlled by the container. In this case, this flag should be reset here (this is its default value).
In the class constructors, in the initialization string to the parent class constructor the values passed in the formal constructor parameters are set, and then the class initialization method is called:
//+------------------------------------------------------------------+ //| CScrollBarThumbH::Конструктор по умолчанию. | //| Строит элемент в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CScrollBarThumbH::CScrollBarThumbH(void) : CButton("SBThumb","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_SCROLLBAR_TH) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| CScrollBarThumbH::Конструктор параметрический. | //| Строит элемент в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CScrollBarThumbH::CScrollBarThumbH(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,text,chart_id,wnd,x,y,w,h) { //--- Инициализация this.Init(""); }
Class Initialization Method:
//+------------------------------------------------------------------+ //| CScrollBarThumbH::Инициализация | //+------------------------------------------------------------------+ void CScrollBarThumbH::Init(const string text) { //--- Инициализация родительского класса CButton::Init(""); //--- Устанавливаем флаги перемещаемости и обновления графика this.SetMovable(true); this.SetChartRedrawFlag(false); }
This element, being attached to another element, may shift. Therefore, the flag of relocation is set for it. The chart redrawing flag is reset by default. It can be installed at any time if necessary.
Cursor Move Handler:
//+------------------------------------------------------------------+ //| CScrollBarThumbH::Обработчик перемещения курсора | //+------------------------------------------------------------------+ void CScrollBarThumbH::OnMoveEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Обработчик перемещения курсора базового объекта CCanvasBase::OnMoveEvent(id,lparam,dparam,sparam); //--- Получаем указатель на базовый объект (элемент управления "горизонтальная полоса прокрутки") CCanvasBase *base_obj=this.GetContainer(); //--- Если для ползунка не установлен флаг перемещаемости, либо указатель на базовый объект не получен - уходим if(!this.IsMovable() || base_obj==NULL) return; //--- Получаем ширину базового объекта и рассчитываем границы пространства для ползунка int base_w=base_obj.Width(); int base_left=base_obj.X()+base_obj.Height(); int base_right=base_obj.Right()-base_obj.Height()+1; //--- Из координат курсора и размеров ползунка рассчитываем ограничения для перемещения int x=(int)lparam-this.m_cursor_delta_x; if(x<base_left) x=base_left; if(x+this.Width()>base_right) x=base_right-this.Width(); //--- Сдвигаем ползунок на рассчитанную координату X if(!this.MoveX(x)) return; //--- Рассчитываем позицию ползунка int thumb_pos=this.X()-base_left; //--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_MOUSE_MOVE, thumb_pos, dparam, this.NameFG()); //--- Перерисовываем график if(this.m_chart_redraw) ::ChartRedraw(this.m_chart_id); }
Here, the dimensions of the Track within which the thumb can move are calculated based on the size of the base object. Next, the thumb moves after the cursor, but subject to track limitations. After that, the thumb offset relative to the left edge of the track is calculated and this value is sent to the chart in the user event, where it is indicated that this is the mouse movement event and the name of the thumb object. These values are necessary for the scrollbar, which owns the thumb, for further work on shifting the contents of the container, which owns the scrollbar, respectively.
Wheel scroll handler:
//+------------------------------------------------------------------+ //| CScrollBarThumbH::Обработчик прокрутки колёсика | //+------------------------------------------------------------------+ void CScrollBarThumbH::OnWheelEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Получаем указатель на базовый объект (элемент управления "горизонтальная полоса прогрутки") CCanvasBase *base_obj=this.GetContainer(); //--- Если для ползунка не установлен флаг перемещаемости, либо указатель на базовый объект не получен - уходим if(!this.IsMovable() || base_obj==NULL) return; //--- Получаем ширину базового объекта и рассчитываем границы пространства для ползунка int base_w=base_obj.Width(); int base_left=base_obj.X()+base_obj.Height(); int base_right=base_obj.Right()-base_obj.Height()+1; //--- Задаём направление смещения в зависимости от направления вращения колёсика мышки int dx=(dparam<0 ? 2 : dparam>0 ? -2 : 0); if(dx==0) dx=(int)lparam; //--- Если при смещении ползунок выйдет за левый край своей области - устанавливаем его на левый край if(dx<0 && this.X()+dx<=base_left) this.MoveX(base_left); //--- иначе, если при смещении ползунок выйдет за правый край своей области - позиционируем его по правому краю else if(dx>0 && this.Right()+dx>=base_right) this.MoveX(base_right-this.Width()); //--- Иначе, если ползунок в пределах своей области - смещаем его на величину смещения else if(this.ShiftX(dx)) this.OnFocusEvent(id,lparam,dparam,sparam); //--- Рассчитываем позицию ползунка int thumb_pos=this.X()-base_left; //--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_MOUSE_WHEEL, thumb_pos, dparam, this.NameFG()); //--- Перерисовываем график if(this.m_chart_redraw) ::ChartRedraw(this.m_chart_id); }
The logic is about the same as that of the previous method. But the wheel scroll event is sent.
Methods of Manipulations With Files:
//+------------------------------------------------------------------+ //| CScrollBarThumbH::Сохранение в файл | //+------------------------------------------------------------------+ bool CScrollBarThumbH::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CButton::Save(file_handle)) return false; //--- Сохраняем флаг обновления графика if(::FileWriteInteger(file_handle,this.m_chart_redraw,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CScrollBarThumbH::Загрузка из файла | //+------------------------------------------------------------------+ bool CScrollBarThumbH::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CButton::Load(file_handle)) return false; //--- Загружаем флаг обновления графика this.m_chart_redraw=::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
Vertical Scrollbar Thumb Class:
//+------------------------------------------------------------------+ //| Класс ползунка вертикальной полосы прокрутки | //+------------------------------------------------------------------+ class CScrollBarThumbV : public CButton { protected: bool m_chart_redraw; // Флаг обновления графика public: //--- (1) Устанавливает, (2) возвращает флаг обновления графика void SetChartRedrawFlag(const bool flag) { this.m_chart_redraw=flag; } bool ChartRedrawFlag(void) const { return this.m_chart_redraw; } //--- Виртуальные методы (1) сохранения в файл, (2) загрузки из файла, (3) тип объекта virtual bool Save(const int file_handle); virtual bool Load(const int file_handle); virtual int Type(void) const { return(ELEMENT_TYPE_SCROLLBAR_THUMB_V); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(const string text); //--- Обработчики событий (1) перемещения курсора, (2) прокрутки колёсика virtual void OnMoveEvent(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); //--- Конструкторы/деструктор CScrollBarThumbV(void); CScrollBarThumbV(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CScrollBarThumbV (void) {} }; //+------------------------------------------------------------------+ //| CScrollBarThumbV::Конструктор по умолчанию. | //| Строит элемент в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CScrollBarThumbV::CScrollBarThumbV(void) : CButton("SBThumb","",::ChartID(),0,0,0,DEF_SCROLLBAR_TH,DEF_PANEL_W) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| CScrollBarThumbV::Конструктор параметрический. | //| Строит элемент в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CScrollBarThumbV::CScrollBarThumbV(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CButton(object_name,text,chart_id,wnd,x,y,w,h) { //--- Инициализация this.Init(""); } //+------------------------------------------------------------------+ //| CScrollBarThumbV::Инициализация | //+------------------------------------------------------------------+ void CScrollBarThumbV::Init(const string text) { //--- Инициализация родительского класса CButton::Init(""); //--- Устанавливаем флаги перемещаемости и обновления графика this.SetMovable(true); this.SetChartRedrawFlag(false); } //+------------------------------------------------------------------+ //| CScrollBarThumbV::Обработчик перемещения курсора | //+------------------------------------------------------------------+ void CScrollBarThumbV::OnMoveEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Обработчик перемещения курсора базового объекта CCanvasBase::OnMoveEvent(id,lparam,dparam,sparam); //--- Получаем указатель на базовый объект (элемент управления "вертикальная полоса прогрутки") CCanvasBase *base_obj=this.GetContainer(); //--- Если для ползунка не установлен флаг перемещаемости, либо указатель на базовый объект не получен - уходим if(!this.IsMovable() || base_obj==NULL) return; //--- Получаем высоту базового объекта и рассчитываем границы пространства для ползунка int base_h=base_obj.Height(); int base_top=base_obj.Y()+base_obj.Width(); int base_bottom=base_obj.Bottom()-base_obj.Width()+1; //--- Из координат курсора и размеров ползунка рассчитываем ограничения для перемещения int y=(int)dparam-this.m_cursor_delta_y; if(y<base_top) y=base_top; if(y+this.Height()>base_bottom) y=base_bottom-this.Height(); //--- Сдвигаем ползунок на рассчитанную координату Y if(!this.MoveY(y)) return; //--- Рассчитываем позицию ползунка int thumb_pos=this.Y()-base_top; //--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_MOUSE_MOVE, thumb_pos, dparam, this.NameFG()); //--- Перерисовываем график if(this.m_chart_redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| CScrollBarThumbV::Обработчик прокрутки колёсика | //+------------------------------------------------------------------+ void CScrollBarThumbV::OnWheelEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Получаем указатель на базовый объект (элемент управления "вертикальная полоса прогрутки") CCanvasBase *base_obj=this.GetContainer(); //--- Если для ползунка не установлен флаг перемещаемости, либо указатель на базовый объект не получен - уходим if(!this.IsMovable() || base_obj==NULL) return; //--- Получаем высоту базового объекта и рассчитываем границы пространства для ползунка int base_h=base_obj.Height(); int base_top=base_obj.Y()+base_obj.Width(); int base_bottom=base_obj.Bottom()-base_obj.Width()+1; //--- Задаём направление смещения в зависимости от направления вращения колёсика мышки int dy=(dparam<0 ? 2 : dparam>0 ? -2 : 0); if(dy==0) dy=(int)lparam; //--- Если при смещении ползунок выйдет за верхний край своей области - устанавливаем его на верхний край if(dy<0 && this.Y()+dy<=base_top) this.MoveY(base_top); //--- иначе, если при смещении ползунок выйдет за нижний край своей области - позиционируем его по нижнему краю else if(dy>0 && this.Bottom()+dy>=base_bottom) this.MoveY(base_bottom-this.Height()); //--- Иначе, если ползунок в пределах своей области - смещаем его на величину смещения else if(this.ShiftY(dy)) this.OnFocusEvent(id,lparam,dparam,sparam); //--- Рассчитываем позицию ползунка int thumb_pos=this.Y()-base_top; //--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam ::EventChartCustom(this.m_chart_id, (ushort)CHARTEVENT_MOUSE_WHEEL, thumb_pos, dparam, this.NameFG()); //--- Перерисовываем график if(this.m_chart_redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| CScrollBarThumbV::Сохранение в файл | //+------------------------------------------------------------------+ bool CScrollBarThumbV::Save(const int file_handle) { //--- Сохраняем данные родительского объекта if(!CButton::Save(file_handle)) return false; //--- Сохраняем флаг обновления графика if(::FileWriteInteger(file_handle,this.m_chart_redraw,INT_VALUE)!=INT_VALUE) return false; //--- Всё успешно return true; } //+------------------------------------------------------------------+ //| CScrollBarThumbV::Загрузка из файла | //+------------------------------------------------------------------+ bool CScrollBarThumbV::Load(const int file_handle) { //--- Загружаем данные родительского объекта if(!CButton::Load(file_handle)) return false; //--- Загружаем флаг обновления графика this.m_chart_redraw=::FileReadInteger(file_handle,INT_VALUE); //--- Всё успешно return true; }
The difference with the previous class is only in calculating constraints for the thumb offset, since here it is shifted vertically. The rest is identical to the horizontal scrollbar thumb class.
Classes CScrollBarThumbH (horizontal thumb) и CScrollBarThumbV (vertical thumb) implement movable elements in user interfaces. The classes are inherited from the button class, support moving the mouse along the scrollbar track, or other limiting element, and also respond to scrolling of the mouse wheel. When the thumb position is changed, classes send an event with the new position, which allows synchronizing the display of the container contents. The thumbs are limited in movement by track boundaries, they can save their state to a file and download it from a file, and control the need to redraw the chart. These classes provide for intuitive and familiar user interaction with scrollable interface areas.
In this context, the thumbs will work as part of the horizontal and vertical scrollbar classes.
Horizontal Scrollbar Class
Continue writing the code in Controls.mqh file:
//+------------------------------------------------------------------+ //| Класс горизонтальной полосы прокрутки | //+------------------------------------------------------------------+ class CScrollBarH : public CPanel { protected: CButtonArrowLeft *m_butt_left; // Кнопка со стрелкой влево CButtonArrowRight*m_butt_right; // Кнопка со стрелкой вправо CScrollBarThumbH *m_thumb; // Ползунок скроллбара public: //--- Возвращает указатель на (1) левую, (2) правую кнопку, (3) ползунок CButtonArrowLeft *GetButtonLeft(void) { return this.m_butt_left; } CButtonArrowRight*GetButtonRight(void) { return this.m_butt_right; } CScrollBarThumbH *GetThumb(void) { return this.m_thumb; } //--- (1) Устанавливает, (2) возвращает флаг обновления графика void SetChartRedrawFlag(const bool flag) { if(this.m_thumb!=NULL) this.m_thumb.SetChartRedrawFlag(flag); } bool ChartRedrawFlag(void) const { return(this.m_thumb!=NULL ? this.m_thumb.ChartRedrawFlag() : false); } //--- Возвращает (1) длину (2) начало трека, (3) позицию ползунка int TrackLength(void) const; int TrackBegin(void) const; int ThumbPosition(void) const; //--- Изменяет размер ползунка bool SetThumbSize(const uint size) const { return(this.m_thumb!=NULL ? this.m_thumb.ResizeW(size) : false); } //--- Изменяет ширину объекта virtual bool ResizeW(const int size); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Тип объекта virtual int Type(void) const { return(ELEMENT_TYPE_SCROLLBAR_H); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Обработчик прокрутки колёсика (Wheel) virtual void OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Конструкторы/деструктор CScrollBarH(void); CScrollBarH(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CScrollBarH(void) {} };
The declared methods show that the class has pointers to left/right arrow buttons and a thumb object. All these objects are implemented in the initialization method of the class. The class implements methods that return pointers to these objects. Methods for setting the flag for updating the chart with the scrollbar thumb, and for getting the status of this flag from the thumb object are also implemented.
In the class constructors, in the initialization string, the values of formal parameters are passed to the parent class constructor. And then the class initialization method is called:
//+------------------------------------------------------------------+ //| CScrollBarH::Конструктор по умолчанию. | //| Строит элемент в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CScrollBarH::CScrollBarH(void) : CPanel("ScrollBarH","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_PANEL_H),m_butt_left(NULL),m_butt_right(NULL),m_thumb(NULL) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CScrollBarH::Конструктор параметрический. | //| Строит элемент в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CScrollBarH::CScrollBarH(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CPanel(object_name,text,chart_id,wnd,x,y,w,h),m_butt_left(NULL),m_butt_right(NULL),m_thumb(NULL) { //--- Инициализация this.Init(); }
Class Initialization Method:
//+------------------------------------------------------------------+ //| CScrollBarH::Инициализация | //+------------------------------------------------------------------+ void CScrollBarH::Init(void) { //--- Инициализация родительского класса CPanel::Init(); //--- Фон - непрозрачный this.SetAlphaBG(255); //--- Ширина рамки и текст this.SetBorderWidth(0); this.SetText(""); //--- Элемент не обрезается по границам контейнера this.m_trim_flag=false; //--- Создаём кнопки прокрутки int w=this.Height(); int h=this.Height(); this.m_butt_left = this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_LEFT, "","ButtL",0,0,w,h); this.m_butt_right= this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_RIGHT,"","ButtR",this.Width()-w,0,w,h); if(this.m_butt_left==NULL || this.m_butt_right==NULL) { ::PrintFormat("%s: Init failed",__FUNCTION__); return; } //--- Настраиваем цвета и вид кнопки со стрелкой влево this.m_butt_left.SetImageBound(1,1,w-2,h-4); this.m_butt_left.InitBackColors(this.m_butt_left.BackColorFocused()); this.m_butt_left.ColorsToDefault(); this.m_butt_left.InitBorderColors(this.BorderColor(),this.m_butt_left.BackColorFocused(),this.m_butt_left.BackColorPressed(),this.m_butt_left.BackColorBlocked()); this.m_butt_left.ColorsToDefault(); //--- Настраиваем цвета и вид кнопки со стрелкой вправо this.m_butt_right.SetImageBound(1,1,w-2,h-4); this.m_butt_right.InitBackColors(this.m_butt_right.BackColorFocused()); this.m_butt_right.ColorsToDefault(); this.m_butt_right.InitBorderColors(this.BorderColor(),this.m_butt_right.BackColorFocused(),this.m_butt_right.BackColorPressed(),this.m_butt_right.BackColorBlocked()); this.m_butt_right.ColorsToDefault(); //--- Создаём ползунок int tsz=this.Width()-w*2; this.m_thumb=this.InsertNewElement(ELEMENT_TYPE_SCROLLBAR_THUMB_H,"","ThumbH",w,1,tsz-w*4,h-2); if(this.m_thumb==NULL) { ::PrintFormat("%s: Init failed",__FUNCTION__); return; } //--- Настраиваем цвета ползунка и устанавливаем ему флаг перемещаемости this.m_thumb.InitBackColors(this.m_thumb.BackColorFocused()); this.m_thumb.ColorsToDefault(); this.m_thumb.InitBorderColors(this.m_thumb.BackColor(),this.m_thumb.BackColorFocused(),this.m_thumb.BackColorPressed(),this.m_thumb.BackColorBlocked()); this.m_thumb.ColorsToDefault(); this.m_thumb.SetMovable(true); //--- запрещаем самостоятельную перерисовку графика this.m_thumb.SetChartRedrawFlag(false); }
Scrollbars are located at the bottom and right of the container outside of its field of view, where the container contents are located. All objects attached to the container are always cropped along the boundaries of the container visible area. Scrollbars are located outside the container visible area, which means they are cropped and become invisible. To avoid this behavior, all objects have a flag indicating the need to crop along the container boundaries. This flag is set by default. Here we set this flag to false i.e. the object will not be cropped along the container boundaries, but its visibility will be controlled by the container class.
In the initialization method, all controls are created and configured — arrow buttons and a thumb. The flag for controlling the chart redrawing with the thumb object is reset — the container class will control redrawing.
Default Object Color Initialization Method:
//+------------------------------------------------------------------+ //| CScrollBarH::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CScrollBarH::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.InitBackColorsAct(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack,clrBlack,clrBlack,clrSilver); this.InitForeColorsAct(clrBlack,clrBlack,clrBlack,clrSilver); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrLightGray,clrLightGray,clrLightGray,clrSilver); this.InitBorderColorsAct(clrLightGray,clrLightGray,clrLightGray,clrSilver); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrSilver); this.InitForeColorBlocked(clrSilver); }
The colors of different states are set to be the same so that there is no change in the color of the element when interacting with the mouse.
A method that draws the appearance:
//+------------------------------------------------------------------+ //| CScrollBarH::Рисует внешний вид | //+------------------------------------------------------------------+ void CScrollBarH::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); //--- Обновляем канвас фона без перерисовки графика this.m_background.Update(false); //--- Рисуем элементы списка без перерисовки графика for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.Draw(false); } //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
A Method That Returns the Track Length:
//+------------------------------------------------------------------+ //| CScrollBarH::Возвращает длину трека | //+------------------------------------------------------------------+ int CScrollBarH::TrackLength(void) const { if(this.m_butt_left==NULL || this.m_butt_right==NULL) return 0; return(this.m_butt_right.X()-this.m_butt_left.Right()); }
It returns the distance in pixels between the X coordinate of the right button and the right edge of the left button.
A Method That Returns the Beginning of the Track:
//+------------------------------------------------------------------+ //| CScrollBarH::Возвращает начало трека | //+------------------------------------------------------------------+ int CScrollBarH::TrackBegin(void) const { return(this.m_butt_left!=NULL ? this.m_butt_left.Width() : 0); }
It returns the offset by the button width over the left edge of the element.
A Method That Returns Thumb Position:
//+------------------------------------------------------------------+ //| CScrollBarH::Возвращает позицию ползунка | //+------------------------------------------------------------------+ int CScrollBarH::ThumbPosition(void) const { return(this.m_thumb!=NULL ? this.m_thumb.X()-this.TrackBegin()-this.X() : 0); }
It returns the thumb offset over the track beginning.
A Method That Changes the Object Width:
//+------------------------------------------------------------------+ //| CScrollBarH::Изменяет ширину объекта | //+------------------------------------------------------------------+ bool CScrollBarH::ResizeW(const int size) { //--- Получаем указатели на левую и правую кнопки if(this.m_butt_left==NULL || this.m_butt_right==NULL) return false; //--- Изменяем ширину объекта if(!CCanvasBase::ResizeW(size)) return false; //--- Смещаем кнопки на новое расположение относительно левой и правой границ изменившего размер элемента if(!this.m_butt_left.MoveX(this.X())) return false; return(this.m_butt_right.MoveX(this.Right()-this.m_butt_right.Width()+1)); }
The horizontal scrollbar can only change its size in width. After resizing the element, the buttons should be moved to their new location so that they are located at the edges of the element.
Wheel scroll handler:
//+------------------------------------------------------------------+ //| CScrollBarH::Обработчик прокрутки колёсика | //+------------------------------------------------------------------+ void CScrollBarH::OnWheelEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Вызываем обработчик прокрутки для ползунка if(this.m_thumb!=NULL) this.m_thumb.OnWheelEvent(id,this.ThumbPosition(),dparam,this.NameFG()); //--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam ::EventChartCustom(this.m_chart_id,CHARTEVENT_MOUSE_WHEEL,this.ThumbPosition(),dparam,this.NameFG()); }
In order for the scrollbar thumb to shift when the wheel is scrolling when the cursor is between the buttons and the thumb, the method delegates event handling to the thumb object and sends the scroll event to the chart.
All other handlers are called when the cursor is above the scrollbar elements — above the buttons and above the thumb. These objects already contain event handlers.
Vertical Scrollbar Class
Vertical scrollbar class is identical to the above horizontal scrollbar class. The only difference is in calculating the length and beginning of the track, and thumb position, as well as in resizing — here the size changes only vertically. Consider the entire class:
//+------------------------------------------------------------------+ //| Класс вертикальной полосы прокрутки | //+------------------------------------------------------------------+ class CScrollBarV : public CPanel { protected: CButtonArrowUp *m_butt_up; // Кнопка со стрелкой вверх CButtonArrowDown *m_butt_down; // Кнопка со стрелкой вниз CScrollBarThumbV *m_thumb; // Ползунок скроллбара public: //--- Возвращает указатель на (1) левую, (2) правую кнопку, (3) ползунок CButtonArrowUp *GetButtonUp(void) { return this.m_butt_up; } CButtonArrowDown *GetButtonDown(void) { return this.m_butt_down; } CScrollBarThumbV *GetThumb(void) { return this.m_thumb; } //--- (1) Устанавливает, (2) возвращает флаг обновления графика void SetChartRedrawFlag(const bool flag) { if(this.m_thumb!=NULL) this.m_thumb.SetChartRedrawFlag(flag); } bool ChartRedrawFlag(void) const { return(this.m_thumb!=NULL ? this.m_thumb.ChartRedrawFlag() : false); } //--- Возвращает (1) длину (2) начало трека, (3) позицию ползунка int TrackLength(void) const; int TrackBegin(void) const; int ThumbPosition(void) const; //--- Изменяет размер ползунка bool SetThumbSize(const uint size) const { return(this.m_thumb!=NULL ? this.m_thumb.ResizeH(size) : false); } //--- Изменяет высоту объекта virtual bool ResizeH(const int size); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Тип объекта virtual int Type(void) const { return(ELEMENT_TYPE_SCROLLBAR_V); } //--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию void Init(void); virtual void InitColors(void); //--- Обработчик прокрутки колёсика (Wheel) virtual void OnWheelEvent(const int id, const long lparam, const double dparam, const string sparam); //--- Конструкторы/деструктор CScrollBarV(void); CScrollBarV(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CScrollBarV(void) {} }; //+------------------------------------------------------------------+ //| CScrollBarV::Конструктор по умолчанию. | //| Строит элемент в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CScrollBarV::CScrollBarV(void) : CPanel("ScrollBarV","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_PANEL_H),m_butt_up(NULL),m_butt_down(NULL),m_thumb(NULL) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CScrollBarV::Конструктор параметрический. | //| Строит элемент в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CScrollBarV::CScrollBarV(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CPanel(object_name,text,chart_id,wnd,x,y,w,h),m_butt_up(NULL),m_butt_down(NULL),m_thumb(NULL) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CScrollBarV::Инициализация | //+------------------------------------------------------------------+ void CScrollBarV::Init(void) { //--- Инициализация родительского класса CPanel::Init(); //--- Фон - непрозрачный this.SetAlphaBG(255); //--- Ширина рамки и текст this.SetBorderWidth(0); this.SetText(""); //--- Элемент не обрезается по границам контейнера this.m_trim_flag=false; //--- Создаём кнопки прокрутки int w=this.Width(); int h=this.Width(); this.m_butt_up = this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_UP, "","ButtU",0,0,w,h); this.m_butt_down= this.InsertNewElement(ELEMENT_TYPE_BUTTON_ARROW_DOWN,"","ButtD",0,this.Height()-w,w,h); if(this.m_butt_up==NULL || this.m_butt_down==NULL) { ::PrintFormat("%s: Init failed",__FUNCTION__); return; } //--- Настраиваем цвета и вид кнопки со стрелкой вверх this.m_butt_up.SetImageBound(1,0,w-4,h-2); this.m_butt_up.InitBackColors(this.m_butt_up.BackColorFocused()); this.m_butt_up.ColorsToDefault(); this.m_butt_up.InitBorderColors(this.BorderColor(),this.m_butt_up.BackColorFocused(),this.m_butt_up.BackColorPressed(),this.m_butt_up.BackColorBlocked()); this.m_butt_up.ColorsToDefault(); //--- Настраиваем цвета и вид кнопки со стрелкой вниз this.m_butt_down.SetImageBound(1,0,w-4,h-2); this.m_butt_down.InitBackColors(this.m_butt_down.BackColorFocused()); this.m_butt_down.ColorsToDefault(); this.m_butt_down.InitBorderColors(this.BorderColor(),this.m_butt_down.BackColorFocused(),this.m_butt_down.BackColorPressed(),this.m_butt_down.BackColorBlocked()); this.m_butt_down.ColorsToDefault(); //--- Создаём ползунок int tsz=this.Height()-w*2; this.m_thumb=this.InsertNewElement(ELEMENT_TYPE_SCROLLBAR_THUMB_V,"","ThumbV",1,w,w-2,tsz/2); if(this.m_thumb==NULL) { ::PrintFormat("%s: Init failed",__FUNCTION__); return; } //--- Настраиваем цвета ползунка и устанавливаем ему флаг перемещаемости this.m_thumb.InitBackColors(this.m_thumb.BackColorFocused()); this.m_thumb.ColorsToDefault(); this.m_thumb.InitBorderColors(this.m_thumb.BackColor(),this.m_thumb.BackColorFocused(),this.m_thumb.BackColorPressed(),this.m_thumb.BackColorBlocked()); this.m_thumb.ColorsToDefault(); this.m_thumb.SetMovable(true); //--- запрещаем самостоятельную перерисовку графика this.m_thumb.SetChartRedrawFlag(false); } //+------------------------------------------------------------------+ //| CScrollBarV::Инициализация цветов объекта по умолчанию | //+------------------------------------------------------------------+ void CScrollBarV::InitColors(void) { //--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона this.InitBackColors(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.InitBackColorsAct(clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke,clrWhiteSmoke); this.BackColorToDefault(); //--- Инициализируем цвета переднего плана для обычного и активированного состояний и делаем его текущим цветом текста this.InitForeColors(clrBlack,clrBlack,clrBlack,clrSilver); this.InitForeColorsAct(clrBlack,clrBlack,clrBlack,clrSilver); this.ForeColorToDefault(); //--- Инициализируем цвета рамки для обычного и активированного состояний и делаем его текущим цветом рамки this.InitBorderColors(clrLightGray,clrLightGray,clrLightGray,clrSilver); this.InitBorderColorsAct(clrLightGray,clrLightGray,clrLightGray,clrSilver); this.BorderColorToDefault(); //--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента this.InitBorderColorBlocked(clrSilver); this.InitForeColorBlocked(clrSilver); } //+------------------------------------------------------------------+ //| CScrollBarV::Рисует внешний вид | //+------------------------------------------------------------------+ void CScrollBarV::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); //--- Обновляем канвас фона без перерисовки графика this.m_background.Update(false); //--- Рисуем элементы списка без перерисовки графика for(int i=0;i<this.m_list_elm.Total();i++) { CElementBase *elm=this.GetAttachedElementAt(i); if(elm!=NULL) elm.Draw(false); } //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); } //+------------------------------------------------------------------+ //| CScrollBarV::Возвращает длину трека | //+------------------------------------------------------------------+ int CScrollBarV::TrackLength(void) const { if(this.m_butt_up==NULL || this.m_butt_down==NULL) return 0; return(this.m_butt_down.Y()-this.m_butt_up.Bottom()); } //+------------------------------------------------------------------+ //| CScrollBarV::Возвращает начало ползунка | //+------------------------------------------------------------------+ int CScrollBarV::TrackBegin(void) const { return(this.m_butt_up!=NULL ? this.m_butt_up.Height() : 0); } //+------------------------------------------------------------------+ //| CScrollBarV::Возвращает позицию ползунка | //+------------------------------------------------------------------+ int CScrollBarV::ThumbPosition(void) const { return(this.m_thumb!=NULL ? this.m_thumb.Y()-this.TrackBegin()-this.Y() : 0); } //+------------------------------------------------------------------+ //| CScrollBarV::Изменяет высоту объекта | //+------------------------------------------------------------------+ bool CScrollBarV::ResizeH(const int size) { //--- Получаем указатели на верхнюю и нижнюю кнопки if(this.m_butt_up==NULL || this.m_butt_down==NULL) return false; //--- Изменяем высоту объекта if(!CCanvasBase::ResizeH(size)) return false; //--- Смещаем кнопки на новое расположение относительно верхней и нижней границ изменившего размер элемента if(!this.m_butt_up.MoveY(this.Y())) return false; return(this.m_butt_down.MoveY(this.Bottom()-this.m_butt_down.Height()+1)); } //+------------------------------------------------------------------+ //| CScrollBarV::Обработчик прокрутки колёсика | //+------------------------------------------------------------------+ void CScrollBarV::OnWheelEvent(const int id,const long lparam,const double dparam,const string sparam) { //--- Вызываем обработчик прокрутки для ползунка if(this.m_thumb!=NULL) this.m_thumb.OnWheelEvent(id,this.ThumbPosition(),dparam,this.NameFG()); //--- Отправляем пользовательское событие на график с позицией ползунка в lparam и именем объекта в sparam ::EventChartCustom(this.m_chart_id,CHARTEVENT_MOUSE_WHEEL,this.ThumbPosition(),dparam,this.NameFG()); }
So, we have everything prepared to create the Container graphical element. Unlike a Panel and a Group of Elements, only one control can be placed in the container, for example, a Panel. Then the container will scroll only the panel with scrollbars, and various controls located on the panel will shift with it, correctly cropping along the boundaries of the container visible area. The visible area is set by four values — the border width at the top, bottom, left, and right.
CContainer is a universal container for user interfaces, designed to accommodate a single large element with the feature to automatically scroll the content horizontally and/or vertically. The class implements the logic of appearance and control of scrollbars depending on the size of a nested element over container visible area.
“Container” Class
Continue writing the code in Controls.mqh file:
//+------------------------------------------------------------------+ //| Класс Контейнер | //+------------------------------------------------------------------+ class CContainer : public CPanel { private: bool m_visible_scrollbar_h; // Флаг видимости горизонтальной полосы прокрутки bool m_visible_scrollbar_v; // Флаг видимости вертикальной полосы прокрутки //--- Возвращает тип элемента, отправившего событие ENUM_ELEMENT_TYPE GetEventElementType(const string name); protected: CScrollBarH *m_scrollbar_h; // Указатель на горизонтальную полосу прокрутки CScrollBarV *m_scrollbar_v; // Указатель на вертикальную полосу прокрутки //--- Проверяет размеры элемента для отображения полос прокрутки void CheckElementSizes(CElementBase *element); //--- Рассчитывает и возвращает размер (1) ползунка, (2) полный, (3) рабочий размер трека горизонтального скроллбара int ThumbSizeHor(void); int TrackLengthHor(void) const { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.TrackLength() : 0); } int TrackEffectiveLengthHor(void) { return(this.TrackLengthHor()-this.ThumbSizeHor()); } //--- Рассчитывает и возвращает размер (1) ползунка, (2) полный, (3) рабочий размер трека вертикального скроллбара int ThumbSizeVer(void); int TrackLengthVer(void) const { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.TrackLength() : 0); } int TrackEffectiveLengthVer(void) { return(this.TrackLengthVer()-this.ThumbSizeVer()); } //--- Размер видимой области содержимого по (1) горизонтали, (2) вертикали int ContentVisibleHor(void) const { return int(this.Width()-this.BorderWidthLeft()-this.BorderWidthRight()); } int ContentVisibleVer(void) const { return int(this.Height()-this.BorderWidthTop()-this.BorderWidthBottom()); } //--- Полный размер содержимого по (1) горизонтали, (2) вертикали int ContentSizeHor(void); int ContentSizeVer(void); //--- Позиция содержимого по (1) горизонтали, (2) вертикали int ContentPositionHor(void); int ContentPositionVer(void); //--- Рассчитывает и возвращает величину смещения содержимого по (1) горизонтали, (2) вертикали в зависимости от положения ползунка int CalculateContentOffsetHor(const uint thumb_position); int CalculateContentOffsetVer(const uint thumb_position); //--- Рассчитывает и возвращает величину смещения ползунка по (1) горизонтали, (2) вертикали в зависимости от положения контента int CalculateThumbOffsetHor(const uint content_position); int CalculateThumbOffsetVer(const uint content_position); //--- Смещает содержимое по (1) горизонтали, (2) вертикали на указанное значение bool ContentShiftHor(const int value); bool ContentShiftVer(const int value); public: //--- Возврат указателей на скроллбары, кнопки и ползунки скроллбаров CScrollBarH *GetScrollBarH(void) { return this.m_scrollbar_h; } CScrollBarV *GetScrollBarV(void) { return this.m_scrollbar_v; } CButtonArrowUp *GetScrollBarButtonUp(void) { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.GetButtonUp() : NULL); } CButtonArrowDown *GetScrollBarButtonDown(void) { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.GetButtonDown() : NULL); } CButtonArrowLeft *GetScrollBarButtonLeft(void) { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.GetButtonLeft() : NULL); } CButtonArrowRight*GetScrollBarButtonRight(void) { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.GetButtonRight(): NULL); } CScrollBarThumbH *GetScrollBarThumbH(void) { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.GetThumb() : NULL); } CScrollBarThumbV *GetScrollBarThumbV(void) { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.GetThumb() : NULL); } //--- Устанавливает флаг прокрутки содержимого void SetScrolling(const bool flag) { this.m_scroll_flag=flag; } //--- Возвращает флаг видимости (1) горизонтального, (2) вертикального скроллбара bool ScrollBarHorIsVisible(void) const { return this.m_visible_scrollbar_h; } bool ScrollBarVerIsVisible(void) const { return this.m_visible_scrollbar_v; } //--- Создаёт и добавляет (1) новый, (2) ранее созданный элемент в список virtual CElementBase *InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h); virtual CElementBase *InsertElement(CElementBase *element,const int dx,const int dy); //--- Рисует внешний вид virtual void Draw(const bool chart_redraw); //--- Тип объекта virtual int Type(void) const { return(ELEMENT_TYPE_CONTAINER); } //--- Обработчики пользовательских событий элемента при наведении курсора, щелчке и прокрутке колёсика в области объекта virtual void MouseMoveHandler(const int id, const long lparam, const double dparam, const string sparam); virtual void MousePressHandler(const int id, const long lparam, const double dparam, const string sparam); virtual void MouseWheelHandler(const int id, const long lparam, const double dparam, const string sparam); //--- Инициализация объекта класса void Init(void); //--- Конструкторы/деструктор CContainer(void); CContainer(const string object_name, const string text, const int x, const int y, const int w, const int h); CContainer(const string object_name, const string text, const int wnd, const int x, const int y, const int w, const int h); CContainer(const string object_name, const string text, const long chart_id, const int wnd, const int x, const int y, const int w, const int h); ~CContainer (void) {} };
When creating a container, scrollbars are immediately created, as well. They are initially hidden, and may appear if the size of the element nested in the container exceeds the width and/or height of container visible area. After scrollbars appear, the position of container contents is automatically controlled using scrollbars.
In the class constructors, in the initialization list, the values of constructor formal parameters are passed to the parent class constructor. And then the class initialization method is called:
//+------------------------------------------------------------------+ //| CContainer::Конструктор по умолчанию. | //| Строит элемент в главном окне текущего графика | //| в координатах 0,0 с размерами по умолчанию | //+------------------------------------------------------------------+ CContainer::CContainer(void) : CPanel("Container","",::ChartID(),0,0,0,DEF_PANEL_W,DEF_PANEL_H), m_visible_scrollbar_h(false), m_visible_scrollbar_v(false) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CContainer::Конструктор параметрический. | //| Строит элемент в главном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CContainer::CContainer(const string object_name,const string text,const int x,const int y,const int w,const int h) : CPanel(object_name,text,::ChartID(),0,x,y,w,h), m_visible_scrollbar_h(false), m_visible_scrollbar_v(false) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CContainer::Конструктор параметрический. | //| Строит элемент в указанном окне текущего графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CContainer::CContainer(const string object_name,const string text,const int wnd,const int x,const int y,const int w,const int h) : CPanel(object_name,text,::ChartID(),wnd,x,y,w,h), m_visible_scrollbar_h(false), m_visible_scrollbar_v(false) { //--- Инициализация this.Init(); } //+------------------------------------------------------------------+ //| CContainer::Конструктор параметрический. | //| Строит элемент в указанном окне указанного графика | //| с указанными текстом, координами и размерами | //+------------------------------------------------------------------+ CContainer::CContainer(const string object_name,const string text,const long chart_id,const int wnd,const int x,const int y,const int w,const int h) : CPanel(object_name,text,chart_id,wnd,x,y,w,h), m_visible_scrollbar_h(false), m_visible_scrollbar_v(false) { //--- Инициализация this.Init(); }
Class Initialization Method:
//+------------------------------------------------------------------+ //| CContainer::Инициализация | //+------------------------------------------------------------------+ void CContainer::Init(void) { //--- Инициализация родительского объекта CPanel::Init(); //--- Ширина рамки this.SetBorderWidth(0); //--- Создаём горизонтальный скроллбар this.m_scrollbar_h=dynamic_cast<CScrollBarH *>(CPanel::InsertNewElement(ELEMENT_TYPE_SCROLLBAR_H,"","ScrollBarH",0,this.Height()-DEF_SCROLLBAR_TH-1,this.Width()-1,DEF_SCROLLBAR_TH)); if(m_scrollbar_h!=NULL) { //--- Скрываем элемент и устанавливаем запрет самостоятельной перерисовки графика this.m_scrollbar_h.Hide(false); this.m_scrollbar_h.SetChartRedrawFlag(false); } //--- Создаём вертикальный скроллбар this.m_scrollbar_v=dynamic_cast<CScrollBarV *>(CPanel::InsertNewElement(ELEMENT_TYPE_SCROLLBAR_V,"","ScrollBarV",this.Width()-DEF_SCROLLBAR_TH-1,0,DEF_SCROLLBAR_TH,this.Height()-1)); if(m_scrollbar_v!=NULL) { //--- Скрываем элемент и устанавливаем запрет самостоятельной перерисовки графика this.m_scrollbar_v.Hide(false); this.m_scrollbar_v.SetChartRedrawFlag(false); } //--- Разрешаем прокрутку содержимого this.m_scroll_flag=true; }
First, initialize the object using the parent class initialization method, then create two hidden scrollbars and set the flag allowing scrolling of the container contents.
Drawing Method:
//+------------------------------------------------------------------+ //| CContainer::Рисует внешний вид | //+------------------------------------------------------------------+ void CContainer::Draw(const bool chart_redraw) { //--- Рисуем внешний вид CPanel::Draw(false); //--- Если прокрутка разрешена if(this.m_scroll_flag) { //--- Если оба скроллбара видимы if(this.m_visible_scrollbar_h && this.m_visible_scrollbar_v) { //--- получаем указатели на две кнопки правого нижнего угла CButtonArrowDown *butt_dn=this.GetScrollBarButtonDown(); CButtonArrowRight*butt_rt=this.GetScrollBarButtonRight(); //--- Получаем указатель на горизонтальную полосу прокрутки и берём цвет её фона CScrollBarH *scroll_bar=this.GetScrollBarH(); color clr=(scroll_bar!=NULL ? scroll_bar.BackColor() : clrWhiteSmoke); //--- Определяем размеры прямоугольника в нижнем правом углу по размерам двух кнопок int bw=(butt_rt!=NULL ? butt_rt.Width() : DEF_SCROLLBAR_TH-3); int bh=(butt_dn!=NULL ? butt_dn.Height(): DEF_SCROLLBAR_TH-3); //--- Устанавливаем координаты, в которых будет нарисован закрашенный прямоугольник int x1=this.Width()-bw-1; int y1=this.Height()-bh-1; int x2=this.Width()-3; int y2=this.Height()-3; //--- Рисуем прямоугольник цветом фона скроллбара в нижнем правом углу this.m_foreground.FillRectangle(x1,y1,x2,y2,::ColorToARGB(clr)); this.m_foreground.Update(false); } } //--- Если указано - обновляем график if(chart_redraw) ::ChartRedraw(this.m_chart_id); }
First, draw the panel, then check the scrollbar visibility flags. If both scrollbars are visible, then it is necessary to draw a filled rectangle with the background color of the scrollbar in the lower right corner at the intersection of the horizontal and vertical scrollbars to create the integrity of their display.
A Method That Implements and Adds a New Element to the List:
//+------------------------------------------------------------------+ //| CContainer::Создаёт и добавляет новый элемент в список | //+------------------------------------------------------------------+ CElementBase *CContainer::InsertNewElement(const ENUM_ELEMENT_TYPE type,const string text,const string user_name,const int dx,const int dy,const int w,const int h) { //--- Проверяем, чтобы в списке было не более трёх объектов - две полосы прокрутки и добавляемый if(this.m_list_elm.Total()>2) { ::PrintFormat("%s: Error. You can only add one element to a container\nTo add multiple elements, use the panel",__FUNCTION__); return NULL; } //--- Создаём и добавляем новый элемент при помощи метода родительского класса //--- Элемент помещается в координаты 0,0 независимо от указанных в параметрах CElementBase *elm=CPanel::InsertNewElement(type,text,user_name,0,0,w,h); //--- Проверяем размеры элемента для отображения полос прокрутки this.CheckElementSizes(elm); //--- Возвращаем указатель на элемент return elm; }
You cannot add more than one element + 2 scrollbars to a container. All the added elements are contained in a single list. Two scrollbars are added to the list when the container is created, and there is only room for one graphical element that can be added to the container. It is this element that will represent the contents of the container and scroll with scrollbars if its dimensions exceed the width and/or height of container visible area. After adding an element, its dimensions are checked to display scrollbars if the element is larger than the visible part of the container.
A Method That Adds a Specified Element to the List:
//+------------------------------------------------------------------+ //| CContainer::Добавляет указанный элемент в список | //+------------------------------------------------------------------+ CElementBase *CContainer::InsertElement(CElementBase *element,const int dx,const int dy) { //--- Проверяем, чтобы в списке было не более трёх объектов - две полосы прокрутки и добавляемый if(this.m_list_elm.Total()>2) { ::PrintFormat("%s: Error. You can only add one element to a container\nTo add multiple elements, use the panel",__FUNCTION__); return NULL; } //--- Добавляем указанный элемент при помощи метода родительского класса //--- Элемент помещается в координаты 0,0 независимо от указанных в параметрах CElementBase *elm=CPanel::InsertElement(element,0,0); //--- Проверяем размеры элемента для отображения полос прокрутки this.CheckElementSizes(elm); //--- Возвращаем указатель на элемент return elm; }
The method works similarly to the previous one, with the only difference being that an element that was already created earlier is added to the list.
A Method That Checks Element Size to Display Scrollbars:
//+------------------------------------------------------------------+ //| CContainer::Проверяет размеры элемента | //| для отображения полос прокрутки | //+------------------------------------------------------------------+ void CContainer::CheckElementSizes(CElementBase *element) { //--- Если передан пустой элемент, или прокрутка запрещена - уходим if(element==NULL || !this.m_scroll_flag) return; //--- Получаем тип элемента и, если это скроллбар - уходим ENUM_ELEMENT_TYPE type=(ENUM_ELEMENT_TYPE)element.Type(); if(type==ELEMENT_TYPE_SCROLLBAR_H || type==ELEMENT_TYPE_SCROLLBAR_V) return; //--- Инициализируем флаги отображения полос прокрутки this.m_visible_scrollbar_h=false; this.m_visible_scrollbar_v=false; //--- Если ширина элемента больше ширины видимой области контейнера - //--- устанавливаем флаг отображения горизонтальной полосы прокрутки if(element.Width()>this.ContentVisibleHor()) this.m_visible_scrollbar_h=true; //--- Если высота элемента больше высоты видимой области контейнера - //--- устанавливаем флаг отображения вертикальной полосы прокрутки if(element.Height()>this.ContentVisibleVer()) this.m_visible_scrollbar_v=true; //--- Если обе полосы прокрутки должны быть отображены if(this.m_visible_scrollbar_h && this.m_visible_scrollbar_v) { //--- Получаем указатели на две кнопки прокрутки в нижнем правом углу CButtonArrowRight *br=this.m_scrollbar_h.GetButtonRight(); CButtonArrowDown *bd=this.m_scrollbar_v.GetButtonDown(); //--- Получаем размеры кнопок прокрутки в высоту и ширину, //--- на которые необходимо уменьшить полосы прокрутки, и int v=(bd!=NULL ? bd.Height() : DEF_SCROLLBAR_TH); int h=(br!=NULL ? br.Width() : DEF_SCROLLBAR_TH); //--- изменяем размеры обеих полос прокрутки на размер кнопок this.m_scrollbar_v.ResizeH(this.m_scrollbar_v.Height()-v); this.m_scrollbar_h.ResizeW(this.m_scrollbar_h.Width() -h); } //--- Если горизонтальная полоса прокрутки должна быть показана if(this.m_visible_scrollbar_h) { //--- Уменьшаем размер видимого окна контейнера снизу на толщину полосы прокрутки + 1 пиксель this.SetBorderWidthBottom(this.m_scrollbar_h.Height()+1); //--- Корректируем размер ползунка под новый размер полосы прокрутки и //--- переносим скроллбар на передний план, делая его при этом видимым this.m_scrollbar_h.SetThumbSize(this.ThumbSizeHor()); this.m_scrollbar_h.BringToTop(false); } //--- Если вертикальная полоса прокрутки должна быть показана if(this.m_visible_scrollbar_v) { //--- Уменьшаем размер видимого окна контейнера справа на толщину полосы прокрутки + 1 пиксель this.SetBorderWidthRight(this.m_scrollbar_v.Width()+1); //--- Корректируем размер ползунка под новый размер полосы прокрутки и //--- переносим скроллбар на передний план, делая его при этом видимым this.m_scrollbar_v.SetThumbSize(this.ThumbSizeVer()); this.m_scrollbar_v.BringToTop(false); } //--- Если любая из полос прокрутки видима - обрезаем привязанный элемент по новым размерам видимой области if(this.m_visible_scrollbar_h || this.m_visible_scrollbar_v) { CElementBase *elm=this.GetAttachedElementAt(2); if(elm!=NULL) elm.ObjectTrim(); } }
The method's logic is explained in comments to the code. The method is called only when an element representing its contents is added to the container.
Methods for Calculating the Size of Scrollbar Thumbs:
//+-------------------------------------------------------------------+ //|CContainer::Рассчитывает размер ползунка горизонтального скроллбара| //+-------------------------------------------------------------------+ int CContainer::ThumbSizeHor(void) { CElementBase *elm=this.GetAttachedElementAt(2); if(elm==NULL || elm.Width()==0 || this.TrackLengthHor()==0) return 0; return int(::round(::fmax(((double)this.ContentVisibleHor() / (double)elm.Width()) * (double)this.TrackLengthHor(), DEF_THUMB_MIN_SIZE))); } //+------------------------------------------------------------------+ //| CContainer::Рассчитывает размер ползунка вертикального скроллбара| //+------------------------------------------------------------------+ int CContainer::ThumbSizeVer(void) { CElementBase *elm=this.GetAttachedElementAt(2); if(elm==NULL || elm.Height()==0 || this.TrackLengthVer()==0) return 0; return int(::round(::fmax(((double)this.ContentVisibleVer() / (double)elm.Height()) * (double)this.TrackLengthVer(), DEF_THUMB_MIN_SIZE))); }
The method calculates the size of the scrollbar thumb so that it is proportional to the ratio of the container visible area to the full size (width/height) of the content. The larger the visible part over the entire content, the larger the thumb will be. The minimum size is limited by the constant DEF_THUMB_MIN_SIZE .
- If there is no content ( elm==NULL or width is 0) or the scrollbar track is of zero length — the method returns 0.
- Otherwise, the method calculates:
(visible container size / full content size) * scrollbar track length. - The result is rounded and compared with the minimum size of the thumb so that it is not too small.
Methods that return the full size of the container contents:
//+------------------------------------------------------------------+ //| CContainer::Полный размер содержимого по горизонтали | //+------------------------------------------------------------------+ int CContainer::ContentSizeHor(void) { CElementBase *elm=this.GetAttachedElementAt(2); return(elm!=NULL ? elm.Width() : 0); } //+------------------------------------------------------------------+ //| CContainer::Полный размер содержимого по вертикали | //+------------------------------------------------------------------+ int CContainer::ContentSizeVer(void) { CElementBase *elm=this.GetAttachedElementAt(2); return(elm!=NULL ? elm.Height() : 0); }
The methods return the width/height of the container contents. If the content could not be retrieved, zero is returned.
Methods That Return the Horizontal/Vertical Position of the Container Contents:
//+--------------------------------------------------------------------+ //|CContainer::Возвращает позицию содержимого контейнера по горизонтали| //+--------------------------------------------------------------------+ int CContainer::ContentPositionHor(void) { CElementBase *elm=this.GetAttachedElementAt(2); return(elm!=NULL ? elm.X()-this.X() : 0); } //+------------------------------------------------------------------+ //|CContainer::Возвращает позицию содержимого контейнера по вертикали| //+------------------------------------------------------------------+ int CContainer::ContentPositionVer(void) { CElementBase *elm=this.GetAttachedElementAt(2); return(elm!=NULL ? elm.Y()-this.Y() : 0); }
The methods return the offset of the origin of the container content over the origin of the container. The upper-left corner is taken as the origin.
Methods That Calculate and Return the Value of Displacement of the Container Contents Based on Thumb Position:
//+------------------------------------------------------------------+ //| CContainer::Рассчитывает и возвращает величину смещения | //| содержимого контейнера по горизонтали по положению ползунка | //+------------------------------------------------------------------+ int CContainer::CalculateContentOffsetHor(const uint thumb_position) { CElementBase *elm=this.GetAttachedElementAt(2); int effective_track_length=this.TrackEffectiveLengthHor(); if(elm==NULL || effective_track_length==0) return 0; return (int)::round(((double)thumb_position / (double)effective_track_length) * ((double)elm.Width() - (double)this.ContentVisibleHor())); } //+------------------------------------------------------------------+ //| CContainer::Рассчитывает и возвращает величину смещения | //| содержимого контейнера по вертикали по положению ползунка | //+------------------------------------------------------------------+ int CContainer::CalculateContentOffsetVer(const uint thumb_position) { CElementBase *elm=this.GetAttachedElementAt(2); int effective_track_length=this.TrackEffectiveLengthVer(); if(elm==NULL || effective_track_length==0) return 0; return (int)::round(((double)thumb_position / (double)effective_track_length) * ((double)elm.Height() - (double)this.ContentVisibleVer())); }
The methods calculate how many pixels the contents of the container should to be shifted depending on the current position of the scrollbar thumb.
- The effective scrollbar track length is determined (the track length minus the thumb size).
- If there is no content or the track is of zero length — 0 is returned.
- The content offset is calculated in proportion to the thumb position:
- Horizontal scrollbar:
(thumb position / track length) * (total width of the content is the width of the visible area) - Vertical scrollbar:
(thumb position / track length) * (total height of the content is the height of the visible area)
- Horizontal scrollbar:
- The result is rounded to the integer.
The methods synchronize the position of the thumb and scrolling of the content: when the user moves the thumb, the content scrolls by the appropriate distance.
Methods That Calculate and Return the Value of Thumb Displacement Depending on Contents Position:
//+------------------------------------------------------------------+ //| CContainer::Рассчитывает и возвращает величину смещения ползунка | //| по горизонтали в зависимости от положения контента | //+------------------------------------------------------------------+ int CContainer::CalculateThumbOffsetHor(const uint content_position) { CElementBase *elm=this.GetAttachedElementAt(2); if(elm==NULL) return 0; int value=elm.Width()-this.ContentVisibleHor(); if(value==0) return 0; return (int)::round(((double)content_position / (double)value) * (double)this.TrackEffectiveLengthHor()); } //+------------------------------------------------------------------+ //| CContainer::Рассчитывает и возвращает величину смещения ползунка | //| по вертикали в зависимости от положения контента | //+------------------------------------------------------------------+ int CContainer::CalculateThumbOffsetVer(const uint content_position) { CElementBase *elm=this.GetAttachedElementAt(2); if(elm==NULL) return 0; int value=elm.Height()-this.ContentVisibleVer(); if(value==0) return 0; return (int)::round(((double)content_position / (double)value) * (double)this.TrackEffectiveLengthVer()); }
The methods calculate the position of the scrollbar thumb (horizontally or vertically) depending on the current offset of the container contents.
- The maximum possible offset of the content is determined (the content size minus the visible area size).
- If there is no content or it fits completely into the container — 0 is returned.
- The thumb position is calculated proportionally to the offset of the content:
- (content offset / maximum offset) * scrollbar track length
- The result is rounded to the integer.
The methods ensure synchronization: when the content is scrolled programmatically or manually, the scrollbar thumb automatically takes the appropriate position on the track.
Methods That Shift the Contents of the Container For the Specified Value:
//+-------------------------------------------------------------------+ //|CContainer::Смещает содержимое по горизонтали на указанное значение| //+-------------------------------------------------------------------+ bool CContainer::ContentShiftHor(const int value) { //--- Получаем указатель на содержимое контейнера CElementBase *elm=this.GetAttachedElementAt(2); if(elm==NULL) return false; //--- Рассчитываем величину смещения по положению ползунка int content_offset=this.CalculateContentOffsetHor(value); //--- Возвращаем результат сдвига содержимого на рассчитанную величину return(elm.MoveX(this.X()-content_offset)); } //+------------------------------------------------------------------+ //| CContainer::Смещает содержимое по вертикали на указанное значение| //+------------------------------------------------------------------+ bool CContainer::ContentShiftVer(const int value) { //--- Получаем указатель на содержимое контейнера CElementBase *elm=this.GetAttachedElementAt(2); if(elm==NULL) return false; //--- Рассчитываем величину смещения по положению ползунка int content_offset=this.CalculateContentOffsetVer(value); //--- Возвращаем результат сдвига содержимого на рассчитанную величину return(elm.MoveY(this.Y()-content_offset)); }
Get a pointer to the container contents, by the thumb position calculate the contents shifting, and return the result of the container contents shifting for the resulting amount.
A Method That Returns the Type of the Scrollbar Element That Sent the Event:
//+------------------------------------------------------------------+ //| Возвращает тип элемента, отправившего событие | //+------------------------------------------------------------------+ ENUM_ELEMENT_TYPE CContainer::GetEventElementType(const string name) { //--- Получаем имена всех элементов в иерархии (при ошибке - возвращаем -1) string names[]={}; int total = GetElementNames(name,"_",names); if(total==WRONG_VALUE) return WRONG_VALUE; //--- Если имя базового элемента в иерархии не совпадает с именем контейнера, то это не наше событие - уходим string base_name=names[0]; if(base_name!=this.NameFG()) return WRONG_VALUE; //--- События, пришедшие не от скроллбаров, пропускаем string check_name=::StringSubstr(names[1],0,4); if(check_name!="SCBH" && check_name!="SCBV") return WRONG_VALUE; //--- Получаем имя элемента, от которого пришло событие и инициализируем тип элемента string elm_name=names[names.Size()-1]; ENUM_ELEMENT_TYPE type=WRONG_VALUE; //--- Проверяем и записываем тип элемента //--- Кнопка со стрелкой вверх if(::StringFind(elm_name,"BTARU")==0) type=ELEMENT_TYPE_BUTTON_ARROW_UP; //--- Кнопка со стрелкой вниз else if(::StringFind(elm_name,"BTARD")==0) type=ELEMENT_TYPE_BUTTON_ARROW_DOWN; //--- Кнопка со стрелкой влево else if(::StringFind(elm_name,"BTARL")==0) type=ELEMENT_TYPE_BUTTON_ARROW_LEFT; //--- Кнопка со стрелкой вправо else if(::StringFind(elm_name,"BTARR")==0) type=ELEMENT_TYPE_BUTTON_ARROW_RIGHT; //--- Ползунок горизонтальной полосы прокрутки else if(::StringFind(elm_name,"THMBH")==0) type=ELEMENT_TYPE_SCROLLBAR_THUMB_H; //--- Ползунок вертикальной полосы прокрутки else if(::StringFind(elm_name,"THMBV")==0) type=ELEMENT_TYPE_SCROLLBAR_THUMB_V; //--- Элемент управления ScrollBarHorisontal else if(::StringFind(elm_name,"SCBH")==0) type=ELEMENT_TYPE_SCROLLBAR_H; //--- Элемент управления ScrollBarVertical else if(::StringFind(elm_name,"SCBV")==0) type=ELEMENT_TYPE_SCROLLBAR_V; //--- Возвращаем тип элемента return type; }
The method determines the type of the element (for example, a scrollbar button, thumb, etc.) that sent the event by the name of this element.
-
The element name is split into parts by the "_" character to get a hierarchy of nesting objects.
-
It checks that the base name (the first element in the hierarchy) matches the name of the current container. If not, the event does not apply to this container, WRONG_VALUE .
-
Next, it is checked that the second element in the hierarchy is a scrollbar (SCBH or SCBV). If not, the event is ignored.
-
The last part of the name (the name of the element itself) determines the element type:
- BTARU — Up Arrow Button
- BTARD — Down Arrow Button
- BTARL — Left Arrow Button
- BTARR — Right Arrow Button
- THMBH — horizontal thumb
- THMBV — vertical thumb
- SCBH — horizontal scrollbar
- SCBV — vertical scrollbar
-
The corresponding element type is returned ( ENUM_ELEMENT_TYPE ). If the type is not defined WRONG_VALUE is returned.
This method allows the container to quickly and reliably understand which scrollbar element triggered the event in order to process it correctly (for example, scroll through the content or shift the thumb).
The handler of a custom element event when moving the cursor in the object area:
//+------------------------------------------------------------------+ //| CContainer::Обработчик пользовательского события элемента | //| при перемещении курсора в области объекта | //+------------------------------------------------------------------+ void CContainer::MouseMoveHandler(const int id,const long lparam,const double dparam,const string sparam) { bool res=false; //--- Получаем указатель на содержимое контейнера CElementBase *elm=this.GetAttachedElementAt(2); //--- Получаем тип элемента, от которого пришло событие ENUM_ELEMENT_TYPE type=this.GetEventElementType(sparam); //--- Если не удалось получить тип элемента или указатель на содержимое - уходим if(type==WRONG_VALUE || elm==NULL) return; //--- Если событие ползунка горизонтального скроллбара - сдвигаем содержимое по горизонтали if(type==ELEMENT_TYPE_SCROLLBAR_THUMB_H) res=this.ContentShiftHor((int)lparam); //--- Если событие ползунка вертикального скроллбара - сдвигаем содержимое по вертикали if(type==ELEMENT_TYPE_SCROLLBAR_THUMB_V) res=this.ContentShiftVer((int)lparam); //--- Если содержимое успешно сдвинуто - обновляем график if(res) ::ChartRedraw(this.m_chart_id); }
Determine the event type and, if the event came from the scrollbar, call the method for shifting the container contents according to the scrollbar type — vertical or horizontal.
The handler of a custom element event when clicking in the object area:
//+------------------------------------------------------------------+ //| CContainer::Обработчик пользовательского события элемента | //| при щелчке в области объекта | //+------------------------------------------------------------------+ void CContainer::MousePressHandler(const int id,const long lparam,const double dparam,const string sparam) { bool res=false; //--- Получаем указатель на содержимое контейнера CElementBase *elm=this.GetAttachedElementAt(2); //--- Получаем тип элемента, от которого пришло событие ENUM_ELEMENT_TYPE type=this.GetEventElementType(sparam); //--- Если не удалось получить тип элемента или указатель на содержимое - уходим if(type==WRONG_VALUE || elm==NULL) return; //--- Если события кнопок горизонтального скроллбара, if(type==ELEMENT_TYPE_BUTTON_ARROW_LEFT || type==ELEMENT_TYPE_BUTTON_ARROW_RIGHT) { //--- Проверяем указатель на горизонтальный скроллбар if(this.m_scrollbar_h==NULL) return; //--- получаем указатель на ползунок скроллбара CScrollBarThumbH *obj=this.m_scrollbar_h.GetThumb(); if(obj==NULL) return; //--- определяем направление смещения ползунка по типу нажатой кнопки int direction=(type==ELEMENT_TYPE_BUTTON_ARROW_LEFT ? 120 : -120); //--- вызываем обработчик прокрутки объекта ползунка для смещения ползунка в направлении direction obj.OnWheelEvent(id,0,direction,this.NameFG()); //--- Успешно res=true; } //--- Если события кнопок вертикального скроллбара, if(type==ELEMENT_TYPE_BUTTON_ARROW_UP || type==ELEMENT_TYPE_BUTTON_ARROW_DOWN) { //--- Проверяем указатель на вертикальный скроллбар if(this.m_scrollbar_v==NULL) return; //--- получаем указатель на ползунок скроллбара CScrollBarThumbV *obj=this.m_scrollbar_v.GetThumb(); if(obj==NULL) return; //--- определяем направление смещения ползунка по типу нажатой кнопки int direction=(type==ELEMENT_TYPE_BUTTON_ARROW_UP ? 120 : -120); //--- вызываем обработчик прокрутки объекта ползунка для смещения ползунка в направлении direction obj.OnWheelEvent(id,0,direction,this.NameFG()); //--- Успешно res=true; } //--- Если событие щелчка по горизонтальному скроллбару (между ползунком и кнопками прокрутки), if(type==ELEMENT_TYPE_SCROLLBAR_H) { //--- Проверяем указатель на горизонтальный скроллбар if(this.m_scrollbar_h==NULL) return; //--- получаем указатель на ползунок скроллбара CScrollBarThumbH *thumb=this.m_scrollbar_h.GetThumb(); if(thumb==NULL) return; //--- Направление смещения ползунка int direction=(lparam>=thumb.Right() ? 1 : lparam<=thumb.X() ? -1 : 0); //--- Проверяем делитель на нулевое значение if(this.ContentSizeHor()-this.ContentVisibleHor()==0) return; //--- Рассчитываем смещение ползунка, пропорциональное смещению содержимого на один экран int thumb_shift=(int)::round(direction * ((double)this.ContentVisibleHor() / double(this.ContentSizeHor()-this.ContentVisibleHor())) * (double)this.TrackEffectiveLengthHor()); //--- вызываем обработчик прокрутки объекта ползунка для смещения ползунка в направлении смещения thumb.OnWheelEvent(id,thumb_shift,0,this.NameFG()); //--- Записываем результат смещения содержимого контейнера res=this.ContentShiftHor(thumb_shift); } //--- Если событие щелчка по вертикальному скроллбару (между ползунком и кнопками прокрутки), if(type==ELEMENT_TYPE_SCROLLBAR_V) { //--- Проверяем указатель на вертикальный скроллбар if(this.m_scrollbar_v==NULL) return; //--- получаем указатель на ползунок скроллбара CScrollBarThumbV *thumb=this.m_scrollbar_v.GetThumb(); if(thumb==NULL) return; //--- Направление смещения ползунка int cursor=int(dparam-this.m_wnd_y); int direction=(cursor>=thumb.Bottom() ? 1 : cursor<=thumb.Y() ? -1 : 0); //--- Проверяем делитель на нулевое значение if(this.ContentSizeVer()-this.ContentVisibleVer()==0) return; //--- Рассчитываем смещение ползунка, пропорциональное смещению содержимого на один экран int thumb_shift=(int)::round(direction * ((double)this.ContentVisibleVer() / double(this.ContentSizeVer()-this.ContentVisibleVer())) * (double)this.TrackEffectiveLengthVer()); //--- вызываем обработчик прокрутки объекта ползунка для смещения ползунка в направлении смещения thumb.OnWheelEvent(id,thumb_shift,0,this.NameFG()); //--- Записываем результат смещения содержимого контейнера res=this.ContentShiftVer(thumb_shift); } //--- Если всё успешно - обновляем график if(res) ::ChartRedraw(this.m_chart_id); }
The method handles mouse clicks on scrollbar elements (buttons, track, thumbs).
- When the buttons are clicked, the thumb shifting handling is delegated to the scrollbar thumb. And as a result, the thumb is shifted and the contents of the container scroll.
- When clicking on a track (between the thumb and scrollbar buttons), the content scrolls to one screen. Handling is delegated to the scrollbar thumb scroll handler. As a result, the contents of the container scroll to one screen.
This method provides for the standard behavior of scrollbars:
- Clicking on the arrow is a step-by-step scroll.
- Clicking on a track scrolls to the page.
- Everything is synchronized with the container contents and the thumb position.
This makes working with the scrollbar familiar and user-friendly.
The handler of a custom element event when scrolling the wheel in the scrollbar thumb area:
//+------------------------------------------------------------------+ //| CContainer::Обработчик пользовательского события элемента | //| при прокрутке колёсика в области ползунка скроллбара | //+------------------------------------------------------------------+ void CContainer::MouseWheelHandler(const int id,const long lparam,const double dparam,const string sparam) { bool res=false; //--- Получаем указатель на содержимое контейнера CElementBase *elm=this.GetAttachedElementAt(2); //--- Получаем тип элемента, от которого пришло событие ENUM_ELEMENT_TYPE type=this.GetEventElementType(sparam); //--- Если не удалось получить указатель на содержимое, или тип элемента - уходим if(type==WRONG_VALUE || elm==NULL) return; //--- Если событие ползунка горизонтального скроллбара - сдвигаем содержимое по горизонтали if(type==ELEMENT_TYPE_SCROLLBAR_THUMB_H) res=this.ContentShiftHor((int)lparam); //--- Если событие ползунка вертикального скроллбара - сдвигаем содержимое по вертикали if(type==ELEMENT_TYPE_SCROLLBAR_THUMB_V) res=this.ContentShiftVer((int)lparam); //--- Если содержимое успешно сдвинуто - обновляем график if(res) ::ChartRedraw(this.m_chart_id); }
The method handles the event of the mouse wheel scrolling over the scrollbar thumb. Depending on which thumb triggered the event — a horizontal or a vertical one, the method shifts the container contents horizontally or vertically by the appropriate distance. After the content has been successfully shifted, the chart is updated.
And for today, this is all that we planned to implement.
Let’s check what we have. Make an indicator in a separate window of the chart. Implement a graphic element "Container", which will contain a "Group of Elements". In the group of elements, create a set of strings from the "Text label" elements. Make the GroupBox element larger than the container to display scrollbars. We will test them.
Testing the Result
In the terminal directory \MQL5\Indicators\ in the Tables\ subfolder, create a new indicator file in the chart subwindow named iTestContainer.mq5. Connect the library to it and declare a pointer to the Container graphical element:
//+------------------------------------------------------------------+ //| iTestContainer.mq5 | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property indicator_separate_window #property indicator_buffers 0 #property indicator_plots 0 //+------------------------------------------------------------------+ //| Включаемые библиотеки | //+------------------------------------------------------------------+ #include "Controls\Controls.mqh" // Библиотека элементов управления CContainer *container=NULL; // Указатель на графический элемент Container //+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Custom deindicator initialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { } //+------------------------------------------------------------------+ //| Custom indicator iteration function | //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) { //--- return value of prev_calculated for next call return(rates_total); } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { } //+------------------------------------------------------------------+ //| Таймер | //+------------------------------------------------------------------+ void OnTimer(void) { }
Create all the elements in indicator’s handler OnInit():
//+------------------------------------------------------------------+ //| Custom indicator initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Ищем подокно графика int wnd=ChartWindowFind(); //--- Создаём графический элемент "Контейнер" container=new CContainer("Container","",0,wnd,100,40,300,200); if(container==NULL) return INIT_FAILED; container.SetID(1); // Идентификатор container.SetAsMain(); // На графике обязательно должен быть один главный элемент container.SetBorderWidth(1); // Ширина рамки (отступ видимой области на один пиксель с каждой стороны контейнера) //--- Присоединяем к контейнеру элемент GroupBox CGroupBox *groupbox=container.InsertNewElement(ELEMENT_TYPE_GROUPBOX,"","Attached Groupbox",4,4,container.Width()*2+20,container.Height()*3+10); if(groupbox==NULL) return INIT_FAILED; groupbox.SetGroup(1); // Номер группы //--- В цикле создаёи и присоединяем к элементу GroupBox 30 строк из элементов "Текстовая метка" for(int i=0;i<30;i++) { string text=StringFormat("This is test line number %d to demonstrate how scrollbars work when scrolling the contents of the container.",(i+1)); int len=groupbox.GetForeground().TextWidth(text); CLabel *lbl=groupbox.InsertNewElement(ELEMENT_TYPE_LABEL,text,"TextString"+string(i+1),8,8+(20*i),len,20); if(lbl==NULL) return INIT_FAILED; } //--- Отрисовываем на графике все созданные элементы и распечатываем их описание в журнале container.Draw(true); container.Print(); //--- Успешно return(INIT_SUCCEEDED); }
In the OnDeinit() handler of the indicator, delete the created Container and the library's shared resource manager:
//+------------------------------------------------------------------+ //| Custom deindicator initialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Удаляем элемент Контейнер и уничтожаем менеджер общих ресурсов библиотеки delete container; CCommonManager::DestroyInstance(); }
In the OnChartEvent() handler, call the similar container handler:
//+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Вызываем обработчик OnChartEvent элемента Контейнер container.OnChartEvent(id,lparam,dparam,sparam); }
In the indicator’s OnTimer() handler, call the container’s OnTimer:
//+------------------------------------------------------------------+ //| Таймер | //+------------------------------------------------------------------+ void OnTimer(void) { //--- Вызываем обработчик OnTimer элемента Контейнер container.OnTimer(); }
Compile the indicator and run it on the chart:

Shifting to full screen when clicking on a track works, shifting when clicking on buttons works, event auto-repeat when holding buttons works, wheel scrolling works.
After all the controls are created, descriptions of all the created elements will be printed out in the log:
Container (ContainerBG, ContainerFG): ID 1, Group -1, x 100, y 40, w 300, h 200 [2]: Groupbox "Attached Groupbox" (ContainerFG_GRBX2BG, ContainerFG_GRBX2FG): ID 2, Group 1, x 100, y 40, w 620, h 610 [0]: Label "TextString1" (ContainerFG_GRBX2FG_LBL0BG, ContainerFG_GRBX2FG_LBL0FG): ID 0, Group 1, x 108, y 48, w 587, h 20 [1]: Label "TextString2" (ContainerFG_GRBX2FG_LBL1BG, ContainerFG_GRBX2FG_LBL1FG): ID 1, Group 1, x 108, y 68, w 587, h 20 [2]: Label "TextString3" (ContainerFG_GRBX2FG_LBL2BG, ContainerFG_GRBX2FG_LBL2FG): ID 2, Group 1, x 108, y 88, w 587, h 20 [3]: Label "TextString4" (ContainerFG_GRBX2FG_LBL3BG, ContainerFG_GRBX2FG_LBL3FG): ID 3, Group 1, x 108, y 108, w 587, h 20 [4]: Label "TextString5" (ContainerFG_GRBX2FG_LBL4BG, ContainerFG_GRBX2FG_LBL4FG): ID 4, Group 1, x 108, y 128, w 587, h 20 [5]: Label "TextString6" (ContainerFG_GRBX2FG_LBL5BG, ContainerFG_GRBX2FG_LBL5FG): ID 5, Group 1, x 108, y 148, w 587, h 20 [6]: Label "TextString7" (ContainerFG_GRBX2FG_LBL6BG, ContainerFG_GRBX2FG_LBL6FG): ID 6, Group 1, x 108, y 168, w 587, h 20 [7]: Label "TextString8" (ContainerFG_GRBX2FG_LBL7BG, ContainerFG_GRBX2FG_LBL7FG): ID 7, Group 1, x 108, y 188, w 587, h 20 [8]: Label "TextString9" (ContainerFG_GRBX2FG_LBL8BG, ContainerFG_GRBX2FG_LBL8FG): ID 8, Group 1, x 108, y 208, w 587, h 20 [9]: Label "TextString10" (ContainerFG_GRBX2FG_LBL9BG, ContainerFG_GRBX2FG_LBL9FG): ID 9, Group 1, x 108, y 228, w 594, h 20 [10]: Label "TextString11" (ContainerFG_GRBX2FG_LBL10BG, ContainerFG_GRBX2FG_LBL10FG): ID 10, Group 1, x 108, y 248, w 594, h 20 [11]: Label "TextString12" (ContainerFG_GRBX2FG_LBL11BG, ContainerFG_GRBX2FG_LBL11FG): ID 11, Group 1, x 108, y 268, w 594, h 20 [12]: Label "TextString13" (ContainerFG_GRBX2FG_LBL12BG, ContainerFG_GRBX2FG_LBL12FG): ID 12, Group 1, x 108, y 288, w 594, h 20 [13]: Label "TextString14" (ContainerFG_GRBX2FG_LBL13BG, ContainerFG_GRBX2FG_LBL13FG): ID 13, Group 1, x 108, y 308, w 594, h 20 [14]: Label "TextString15" (ContainerFG_GRBX2FG_LBL14BG, ContainerFG_GRBX2FG_LBL14FG): ID 14, Group 1, x 108, y 328, w 594, h 20 [15]: Label "TextString16" (ContainerFG_GRBX2FG_LBL15BG, ContainerFG_GRBX2FG_LBL15FG): ID 15, Group 1, x 108, y 348, w 594, h 20 [16]: Label "TextString17" (ContainerFG_GRBX2FG_LBL16BG, ContainerFG_GRBX2FG_LBL16FG): ID 16, Group 1, x 108, y 368, w 594, h 20 [17]: Label "TextString18" (ContainerFG_GRBX2FG_LBL17BG, ContainerFG_GRBX2FG_LBL17FG): ID 17, Group 1, x 108, y 388, w 594, h 20 [18]: Label "TextString19" (ContainerFG_GRBX2FG_LBL18BG, ContainerFG_GRBX2FG_LBL18FG): ID 18, Group 1, x 108, y 408, w 594, h 20 [19]: Label "TextString20" (ContainerFG_GRBX2FG_LBL19BG, ContainerFG_GRBX2FG_LBL19FG): ID 19, Group 1, x 108, y 428, w 594, h 20 [20]: Label "TextString21" (ContainerFG_GRBX2FG_LBL20BG, ContainerFG_GRBX2FG_LBL20FG): ID 20, Group 1, x 108, y 448, w 594, h 20 [21]: Label "TextString22" (ContainerFG_GRBX2FG_LBL21BG, ContainerFG_GRBX2FG_LBL21FG): ID 21, Group 1, x 108, y 468, w 594, h 20 [22]: Label "TextString23" (ContainerFG_GRBX2FG_LBL22BG, ContainerFG_GRBX2FG_LBL22FG): ID 22, Group 1, x 108, y 488, w 594, h 20 [23]: Label "TextString24" (ContainerFG_GRBX2FG_LBL23BG, ContainerFG_GRBX2FG_LBL23FG): ID 23, Group 1, x 108, y 508, w 594, h 20 [24]: Label "TextString25" (ContainerFG_GRBX2FG_LBL24BG, ContainerFG_GRBX2FG_LBL24FG): ID 24, Group 1, x 108, y 528, w 594, h 20 [25]: Label "TextString26" (ContainerFG_GRBX2FG_LBL25BG, ContainerFG_GRBX2FG_LBL25FG): ID 25, Group 1, x 108, y 548, w 594, h 20 [26]: Label "TextString27" (ContainerFG_GRBX2FG_LBL26BG, ContainerFG_GRBX2FG_LBL26FG): ID 26, Group 1, x 108, y 568, w 594, h 20 [27]: Label "TextString28" (ContainerFG_GRBX2FG_LBL27BG, ContainerFG_GRBX2FG_LBL27FG): ID 27, Group 1, x 108, y 588, w 594, h 20 [28]: Label "TextString29" (ContainerFG_GRBX2FG_LBL28BG, ContainerFG_GRBX2FG_LBL28FG): ID 28, Group 1, x 108, y 608, w 594, h 20 [29]: Label "TextString30" (ContainerFG_GRBX2FG_LBL29BG, ContainerFG_GRBX2FG_LBL29FG): ID 29, Group 1, x 108, y 628, w 594, h 20
The stated functionality operates correctly. There are some minor flaws, but they will be removed with further development of the TableView control.
Conclusion
Today we have implemented a fairly extensive and necessary functionality in the controls library being developed.
The CContainer class is a powerful and convenient tool for creating scrollable areas in user interfaces. It automates the operation of scrollbars, facilitates the management of large content, and provides user-friendly interaction with scrollable areas. Due to its flexible architecture and integration with other interface elements, the container is easy to use as part of complex graphical solutions.
Our next step will be to create a header that allows placing, for example, a list of table column headers, while the functionality will allow resizing each header cell. This will automatically resize the table columns.
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 | iTestContainer.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 |
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/18658
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.
Fortified Profit Architecture: Multi-Layered Account Protection
Automating Trading Strategies in MQL5 (Part 45): Inverse Fair Value Gap (IFVG)
Mastering Kagi Charts in MQL5 (Part 2): Implementing Automated Kagi-Based Trading
Chaos Game Optimization (CGO)
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use