Русский
preview
The View and Controller components for tables in the MQL5 MVC paradigm: Resizable elements

The View and Controller components for tables in the MQL5 MVC paradigm: Resizable elements

MetaTrader 5Examples |
2 246 0
Artyom Trishkin
Artyom Trishkin

Contents


Introduction

In modern user interfaces, a feature to resize elements with the mouse is a familiar and expected one. The user can "grab" the border of a window, panel, or other visual block and drag it, resizing the element in real time. Such interactivity requires a well-thought-out architecture to ensure responsiveness and correct handling of all events.

One of the popular architectural approaches for building complex interfaces is MVC (Model-View-Controller). In this paradigm:

  • Model is responsible for data and logic,
  • View is responsible for displaying data and visual interaction with the user,
  • Controller is responsible for handling user events and communication between the Model and the View.

Within the context of resizing elements with the mouse, the main work takes place precisely at the level of the View component. It implements a visual representation of the element, tracks mouse movements, determines whether the cursor is on the boundary, and displays appropriate tooltips (for example, changing the cursor shape). The component is also responsible for rendering the resized element during the resizing process when being dragged.

The Controller component can participate in processing mouse events by passing commands to the View component and, if necessary, updating the Model component (for example, if the element dimensions should be saved or if they affect other data).

Implementing mouse-based resizing is a typical example of how the View component operates in the MVC architecture, where visual interaction and user feedback are implemented as interactively and visually as possible.

Visual tables (TableView, DataGrid, Spreadsheet, etc.) are one of the key elements of modern interfaces used for displaying and editing tabular data. The user expects that the table will not only display the data, but also provide him with convenient tools to customize the appearance for his tasks.

A feature to resize a table and its individual parts (column widths, line heights, and sizes of the entire table area) using the mouse is the de facto standard for the TableView control in professional applications. Availability of such functionality allows to:

  • Adapt the interface to the volume and structure of the data. The user can expand a column with long values or narrow down uninformative columns.
  • Improve the readability and perception of information. Flexible size adjustment helps to avoid horizontal scrolling and redundant empty areas.
  • Create a sense of a "live" interface, familiar from office and analytical programs.
  • Implement complex data scenarios where the sizes of cells, rows, and columns can change dynamically.

Without resizing support, the TableView element becomes static and inconvenient for real work with data. Therefore, implementation of the mechanism for resizing elements with the mouse is an integral part of creating a modern, convenient and professional component of the table.

Today we will add all the elements with a feature to resize them by dragging the edges and corners of the element with the mouse. At the same time, graphical tooltips will appear in the cursor area — arrows pointing in the direction of possible resizing. When you hover the cursor over the dragging area and click (capture the area), the resizing mode will be enabled. When you release the mouse, the mode will turn off. All flags (activation of the movement mode and the direction of resizing) will be fixed in the class of shared resources and are readable in each graphical element.

We will add new properties to all the elements, allowing them to be resized.

To implement this functionality, refining the already created classes and adding a new one to create tooltips will only be required. Tooltips are a type of graphical elements that, after a short delay, automatically appear when the mouse cursor is hovered over a certain area of a graphical element. They contain a description text, a graphical image, or both. Based on this class, we can create other tooltips. For example, images of arrows appearing near the cursor and indicating the direction of resizing.

Today we will make just such types of tooltips, namely, double horizontal, vertical and diagonal arrows indicating the direction of movement of edges and corners of the graphical element. Text tooltips can be implemented after the TableView control is created for the visual design of its cells, columns, and headers.

Continue writing codes in the library files located at \MQL5\Indicators\Tables\Controls\. The previous version of all files is available in the previous article. Files Base.mqh and Control.mqh will be refined.


Refining Base Classes

Open the Base.mqh file and enter the forward declaration of the tooltip class:

//+------------------------------------------------------------------+
//|                                                         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    CVisualHint;                     // Класс подсказки
class    CLabel;                          // Класс текстовой метки
class    CButton;                         // Класс простой кнопки
class    CButtonTriggered;                // Класс двухпозиционной кнопки
class    CButtonArrowUp;                  // Класс кнопки со стрелкой вверх
class    CButtonArrowDown;                // Класс кнопки со стрелкой вниз
class    CButtonArrowLeft;                // Класс кнопки со стрелкой влево
class    CButtonArrowRight;               // Класс кнопки со стрелкой вправо
class    CCheckBox;                       // Класс элемента управления CheckBox
class    CRadioButton;                    // Класс элемента управления RadioButton
class    CScrollBarThumbH;                // Класс ползунка горизонтальной полосы прокрутки
class    CScrollBarThumbV;                // Класс ползунка вертикальной полосы прокрутки
class    CScrollBarH;                     // Класс горизонтальной полосы прокрутки
class    CScrollBarV;                     // Класс вертикальной полосы прокрутки
class    CPanel;                          // Класс элемента управления Panel
class    CGroupBox;                       // Класс элемента управления GroupBox
class    CContainer;                      // Класс элемента управления Container

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

Each element should have a certain area along its edges, and when you hover the mouse cursor over it, the resizing of the object should be activated. Enter the thickness of this zone in the macro substitution block:

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

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+

Add a new type of “hint object" to the enumeration of graphical element types:

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
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_HINT,                     // Подсказка
   ELEMENT_TYPE_LABEL,                    // Текстовая метка
   ELEMENT_TYPE_BUTTON,                   // Простая кнопка
   ELEMENT_TYPE_BUTTON_TRIGGERED,         // Двухпозиционная кнопка
   ELEMENT_TYPE_BUTTON_ARROW_UP,          // Кнопка со стрелкой вверх
   ELEMENT_TYPE_BUTTON_ARROW_DOWN,        // Кнопка со стрелкой вниз
   ELEMENT_TYPE_BUTTON_ARROW_LEFT,        // Кнопка со стрелкой влево
   ELEMENT_TYPE_BUTTON_ARROW_RIGHT,       // Кнопка со стрелкой вправо
   ELEMENT_TYPE_CHECKBOX,                 // Элемент управления CheckBox
   ELEMENT_TYPE_RADIOBUTTON,              // Элемент управления RadioButton
   ELEMENT_TYPE_SCROLLBAR_THUMB_H,        // Ползунок горизонтальной полосы прокрутки
   ELEMENT_TYPE_SCROLLBAR_THUMB_V,        // Ползунок вертикальной полосы прокрутки
   ELEMENT_TYPE_SCROLLBAR_H,              // Элемент управления ScrollBarHorisontal
   ELEMENT_TYPE_SCROLLBAR_V,              // Элемент управления ScrollBarVertical
   ELEMENT_TYPE_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   // Максимальное значение списка активных элементов

Interaction of the cursor with an element in the context of resizing uses certain concepts, such as the cursor location on one of the boundaries of the element or on its corners, as well as the action being performed at a given time.

Add new enumerations to describe such actions and values:

enum ENUM_CURSOR_REGION                   // Перечисление расположения курсора на границах элемента
  {
   CURSOR_REGION_NONE,                    // Нет
   CURSOR_REGION_TOP,                     // На верхней грани
   CURSOR_REGION_BOTTOM,                  // На нижней грани
   CURSOR_REGION_LEFT,                    // На левой грани
   CURSOR_REGION_RIGHT,                   // На правой грани
   CURSOR_REGION_LEFT_TOP,                // В левом верхнем углу
   CURSOR_REGION_LEFT_BOTTOM,             // В левом нижнем углу
   CURSOR_REGION_RIGHT_TOP,               // В правом верхнем углу
   CURSOR_REGION_RIGHT_BOTTOM,            // В правом нижнем углу
  };
  
enum ENUM_RESIZE_ZONE_ACTION              // Перечисление взаимодействий с зоной перетаскивания элемента
  {
   RESIZE_ZONE_ACTION_NONE,               // Нет
   RESIZE_ZONE_ACTION_HOVER,              // Наведение курсора на зону
   RESIZE_ZONE_ACTION_BEGIN,              // Начало перетаскивания
   RESIZE_ZONE_ACTION_DRAG,               // Процесс перетаскивания
   RESIZE_ZONE_ACTION_END                 // Завершение перетаскивания
  };  
  
//+------------------------------------------------------------------+ 
//| Функции                                                          |
//+------------------------------------------------------------------+

The interaction of the cursor with the element boundaries is divided into five milestones:

  1. No interaction. Element events are handled in the usual way.
  2. The cursor is hovered over the resizing area. Arrow-tooltips should be shown next to the cursor pointing in the direction of possible resizing. Here you can also set a global flag that prohibits other elements from reacting to mouse interaction events. This item has not been implemented at the moment.
  3. The user has just clicked the mouse button, thereby having captured the graphical element interaction area. The public flag of the active resizing mode is set by dragging the captured edge or corner, arrow tooltips are displayed, and the value of the movement direction is indicated in the shared resource manager. A handler for resizing a graphical element is called.
  4. The user moves the cursor with the captured element’s edge or corner. The direction of dragging the face is set in the general resource manager. Depending on this value a handler for resizing a graphical element is called, arrow tooltips that follow the cursor continue to be displayed.
  5. As soon as the user releases the mouse button while resizing mode is active, all the flags set in the shared resources manager are reset and the arrow tooltips get hidden. The element now has a new size, which was changed after moving the cursor in handlers for resizing the graphical element.

This logic will be implemented today. We will not implement the above mentioned flag, which prohibits other elements from reacting to mouse interaction events, since this refers more to service functions for simplifying manipulations with the resizing functionality by using the edge dragging method.

For example, if, say, a scrollbar is in contact with the lower edge of an element, then when the cursor hovers over this edge, the scrollbar can also react to interaction with the cursor. And instead of dragging the edge, we activate scrolling of the container contents, as the scrollbar will take over control. At the same time, where have we seen the elements that have no area to capture? Probably only somewhere in the unfinished controls (like here at the moment). Implementation of such service functionality will complicate the already complicated code of graphical element classes.

Add a new value of the name to the function that returns a short name of the element by type:

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

In the shared resource manager class, add a feature to retrieve and return mouse cursor coordinates, the resizing mode flag, and the element edge:

//+------------------------------------------------------------------+
//| Класс-синглтон для общих флагов и событий графических элементов  |
//+------------------------------------------------------------------+
class CCommonManager
  {
private:
   static CCommonManager *m_instance;                          // Экземпляр класса
   string            m_element_name;                           // Имя активного элемента
   int               m_cursor_x;                               // Координата X курсора
   int               m_cursor_y;                               // Координата Y курсора
   bool              m_resize_mode;                            // Режим изменения размеров
   ENUM_CURSOR_REGION m_resize_region;                         // Грань элемента, за которую изменяется размер
   
//--- Конструктор/деструктор
                     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; }
   
//--- (1) Устанавливает, (2) возвращает координату X курсора
   void              SetCursorX(const int x)                      { this.m_cursor_x=x;          }
   int               CursorX(void)                          const { return this.m_cursor_x;     }
   
//--- (1) Устанавливает, (2) возвращает координату Y курсора
   void              SetCursorY(const int y)                      { this.m_cursor_y=y;          }
   int               CursorY(void)                          const { return this.m_cursor_y;     }
   
//--- (1) Устанавливает, (2) возвращает режим изменения размеров
   void              SetResizeMode(const bool flag)               { this.m_resize_mode=flag;    }
   bool              ResizeMode(void)                       const { return this.m_resize_mode;  }
   
//--- (1) Устанавливает, (2) возвращает грань элемента
   void              SetResizeRegion(const ENUM_CURSOR_REGION edge){ this.m_resize_region=edge; }
   ENUM_CURSOR_REGION ResizeRegion(void)                    const { return this.m_resize_region;}

  };
//--- Инициализация статической переменной экземпляра класса
CCommonManager* CCommonManager::m_instance=NULL;

In the event handler, cursor coordinates will be written to class variables, and they will be available anywhere in the program, which simplifies access to coordinates and their use in controls. Similarly, by writing to variables the resizing mode flag and the edge of the element that the cursor interacts with, we enable all elements to "see" this mode and handle it accordingly.

Make refinements to the base class of the graphical elements canvas. Declare a flag indicating that the element size can be changed interactively:

//+------------------------------------------------------------------+
//| Базовый класс холста графических элементов                       |
//+------------------------------------------------------------------+
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_resizable;                              // Флаг разрешения изменения размеров
   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-ордер графического объекта

Add methods that allow you to set and receive the resizing mode flag and the interaction zone from the shared resource manager:

//--- (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();                                   }
   
//--- (1) Устанавливает, (2) возвращает флаг режима изменения размеров
   void              SetResizeMode(const bool flag)            { CCommonManager::GetInstance().SetResizeMode(flag);                                }
   bool              ResizeMode(void)                    const { return CCommonManager::GetInstance().ResizeMode();                                }
   
//--- (1) Устанавливает, (2) возвращает грань элемента, за которую изменяется размер
   void              SetResizeRegion(const ENUM_CURSOR_REGION edge){ CCommonManager::GetInstance().SetResizeRegion(edge);                          }
   ENUM_CURSOR_REGION ResizeRegion(void)                 const { return CCommonManager::GetInstance().ResizeRegion();                              }
   
//--- Возврат смещения начальных координат рисования на холсте относительно канваса и координат объекта
 

Now each graphical element will be able to set and receive data about the resizing mode common to all elements.

When changing the element size by dragging over the left or top edge, or over the corners bordering on these edges, you must also shift the coordinates along with resizing the element. Testing the sequential application of separate methods for shifting coordinates and resizing an element suggests that within the interval between calls of two methods, it is possible to update the chart by the terminal with redrawing. This leads to the fact that while dragging the edges of the element to resize, we see artifacts on the chart in the form of flashes of the previous, unchanged size of the element.

To avoid such unpleasant visual effects, it is necessary to reduce the delay between resizing and shifting the coordinate. To do this, implement (declare) a separate method where both the size and the coordinate of the element will be changed immediately:

//--- Устанавливает координату (1) X, (2) Y, (3) обе координаты графического объекта
   bool              ObjectSetX(const int x);
   bool              ObjectSetY(const int y);
   bool              ObjectSetXY(const int x,const int y)      { return(this.ObjectSetX(x) && this.ObjectSetY(y));                                 }
   
//--- Устанавливает одновременно координаты и размеры графического объекта
   virtual bool      ObjectSetXYWidthResize(const int x,const int y,const int w,const int h);

We need a method that will return the cursor location within the boundaries of the graphical element. Declare such a method:

//--- (1) Устанавливает, (2) смещает графический объект на указанные координаты/размер смещения
   bool              ObjectMove(const int x,const int y)       { return this.ObjectSetXY(x,y);                                                     }
   bool              ObjectShift(const int dx,const int dy)    { return this.ObjectSetXY(this.ObjectX()+dx,this.ObjectY()+dy);                     }
   
//--- Возвращает флаг нахождения курсора внутри объекта
   bool              Contains(const int x,const int y);
//--- Возвращает место нахождения курсора на границах объекта
   ENUM_CURSOR_REGION CheckResizeZone(const int x,const int y);

Declare virtual handlers to handle cursor interaction events at the boundaries of an element to resize it:

//--- Обработчики событий (1) наведения курсора (Focus), (2) нажатий кнопок мышки (Press),
//--- (3) перемещения курсора (Move), (4) ухода из фокуса (Release), (5) создания графического объекта (Create),
//--- (6) прокрутки колёсика (Wheel), (7) изменение размеров (Resize). Переопределяются в наследниках.
   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      OnResizeZoneEvent(const int id, const long lparam, const double dparam, const string sparam)    { return;         }  // обработчик здесь отключен
   
//--- Обработчики изменения размеров элемента по сторонам и углам
   virtual bool      OnResizeZoneLeft(const int x, const int y)                                                      { return false;   }  // обработчик здесь отключен
   virtual bool      OnResizeZoneRight(const int x, const int y)                                                     { return false;   }  // обработчик здесь отключен
   virtual bool      OnResizeZoneTop(const int x, const int y)                                                       { return false;   }  // обработчик здесь отключен
   virtual bool      OnResizeZoneBottom(const int x, const int y)                                                    { return false;   }  // обработчик здесь отключен
   virtual bool      OnResizeZoneLeftTop(const int x, const int y)                                                   { return false;   }  // обработчик здесь отключен
   virtual bool      OnResizeZoneRightTop(const int x, const int y)                                                  { return false;   }  // обработчик здесь отключен
   virtual bool      OnResizeZoneLeftBottom(const int x, const int y)                                                { return false;   }  // обработчик здесь отключен
   virtual bool      OnResizeZoneRightBottom(const int x, const int y)                                               { return false;   }  // обработчик здесь отключен

We will implement these handlers in inherited classes.

Add methods that return some object flags that were not previously done:

//--- Возвращает (1) принадлежность объекта программе, флаг (2) скрытого, (3) заблокированного,
//--- (4) перемещаемого, (5) изменяемого в размерах, (6) главного элемента, (7) в фокусе, (8, 9) имя графического объекта (фон, текст)
   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              IsResizable(void)                   const { return this.m_resizable;                                                          }
   bool              IsMain(void)                        const { return this.m_main;                                                               }
   bool              IsFocused(void)                     const { return this.m_focused;                                                            }
   bool              IsAutorepeat(void)                  const { return this.m_autorepeat_flag;                                                    }
   bool              IsScrollable(void)                  const { return this.m_scroll_flag;                                                        }
   bool              IsTrimmed(void)                     const { return this.m_trim_flag;                                                          }
   string            NameBG(void)                        const { return this.m_background.ChartObjectName();                                       }
   string            NameFG(void)                        const { return this.m_foreground.ChartObjectName();                                       }

and methods for setting these flags:

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

Declare a method that simultaneously resizes the element and shifts it to new coordinates:

//--- Устанавливает объекту новую координату (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);
   
//--- Устанавливает одновременно координаты и размеры элемента
   virtual bool      MoveXYWidthResize(const int x,const int y,const int w,const int h);

In constructors of the class, in the initialization list, set the default value for the resizing flag of the element:

//--- Конструкторы/деструктор
                     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_resizable(false), m_main(false), m_autorepeat_flag(false), m_trim_flag(true), m_scroll_flag(false),
                        m_border_width_lt(0), m_border_width_rt(0), m_border_width_up(0), m_border_width_dn(0), m_z_order(0),
                        m_state(0), m_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_resizable(false), m_main(false), m_autorepeat_flag(false), m_trim_flag(true), m_scroll_flag(false),
   m_border_width_lt(0), m_border_width_rt(0), m_border_width_up(0), m_border_width_dn(0), m_z_order(0),
   m_state(0), m_cursor_delta_x(0), m_cursor_delta_y(0)
  {
//--- Получаем скорректированный идентификатор графика и дистанцию в пикселях по вертикальной оси Y
//--- между верхней рамкой подокна индикатора и верхней рамкой главного окна графика
   this.m_chart_id=this.CorrectChartID(chart_id);
   
//--- Если графический ресурс и графический объект созданы
   if(this.Create(this.m_chart_id,this.m_wnd,object_name,x,y,w,h))
     {
      //--- Очищаем канвасы фона и переднего плана и устанавливаем начальные значения координат,
      //--- наименования графических объектов и свойства текста, рисуемого на переднем плане
      this.Clear(false);
      this.m_obj_x=x;
      this.m_obj_y=y;
      this.m_color_background.SetName("Background");
      this.m_color_foreground.SetName("Foreground");
      this.m_color_border.SetName("Border");
      this.m_foreground.FontSet(DEF_FONTNAME,-DEF_FONTSIZE*10,FW_MEDIUM);
      this.m_bound.SetName("Perimeter");
      
      //--- Запоминаем разрешения для мышки и инструментов графика
      this.Init();
     }
  }

Outside of the class body, write the declared methods.

A Method That Returns Cursor's Location on Object Boundaries:

//+--------------------------------------------------------------------+
//|CCanvasBase::Возвращает место нахождения курсора на границах объекта|
//+--------------------------------------------------------------------+
ENUM_CURSOR_REGION CCanvasBase::CheckResizeZone(const int x,const int y)
  {
//--- Координаты границ элемента
   int top=this.Y();
   int bottom=this.Bottom();
   int left=this.X();
   int right=this.Right();
   
//--- Если за пределами объекта - возвращаем CURSOR_REGION_NONE
   if(x<left || x>right || y<top || y>bottom)
      return CURSOR_REGION_NONE;

//--- Левая грань и углы
   if(x>=left && x<=left+DEF_EDGE_THICKNESS)
     {
      //--- Левый верхний угол
      if(y>=top && y<=top+DEF_EDGE_THICKNESS)
         return CURSOR_REGION_LEFT_TOP;
      //--- Левый нижний угол
      if(y>=bottom-DEF_EDGE_THICKNESS && y<=bottom)
         return CURSOR_REGION_LEFT_BOTTOM;
      //--- Левая грань
      return CURSOR_REGION_LEFT;
     }
   
//--- Правая грань и углы
   if(x>=right-DEF_EDGE_THICKNESS && x<=right)
     {
      //--- Правый верхний угол
      if(y>=top && y<=top+DEF_EDGE_THICKNESS)
         return CURSOR_REGION_RIGHT_TOP;
      //--- Правый нижний угол
      if(y>=bottom-DEF_EDGE_THICKNESS && y<=bottom)
         return CURSOR_REGION_RIGHT_BOTTOM;
      //--- Правая грань
      return CURSOR_REGION_RIGHT;
     }
     
//--- Верхняя грань
   if(y>=top && y<=top+DEF_EDGE_THICKNESS)
      return CURSOR_REGION_TOP;

//--- Нижняя грань
   if(y>=bottom-DEF_EDGE_THICKNESS && y<=bottom)
      return CURSOR_REGION_BOTTOM;

//--- Курсор не на гранях элемента
   return CURSOR_REGION_NONE;
  }

The method checks whether the cursor is within a narrow bar of DEF_EDGE_THICKNESS thickness around the perimeter of element boundaries and returns the face or angle where the cursor falls.

A Method That Simultaneously Sets the Coordinates And Dimensions of a Graphical Object:

//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает одновременно                          |
//| координаты и размеры графического объекта                        |
//+------------------------------------------------------------------+
bool CCanvasBase::ObjectSetXYWidthResize(const int x,const int y,const int w,const int h)
  {
//--- Если новые координаты установлены - возвращаем результат изменения размеров
   if(this.ObjectSetXY(x,y))
      return this.ObjectResize(w,h);
//--- Не удалось установить новые координаты - возвращаем false
   return false;
  }

If coordinates of the object are successfully set, the result of resizing the graphical object is returned. The methods working inside this method address the properties of the graphical object directly, which results in a lower lag than when using methods that resize an element and move it to new coordinates, since they additionally perform other operations with its properties. 

A Method That Simultaneously Sets theCoordinates And Dimensions of an Element:

//+------------------------------------------------------------------+
//| CCanvasBase::Устанавливает и координаты, и размеры элемента      |
//+------------------------------------------------------------------+
bool CCanvasBase::MoveXYWidthResize(const int x,const int y,const int w,const int h)
  {
   if(!this.ObjectSetXYWidthResize(x,y,w,h))
      return false;
   this.BoundMove(x,y);
   this.BoundResize(w,h);
   if(!this.ObjectTrim())
     {
      this.Update(false);
      this.Draw(false);
     }
   return true;
  }

First, a method is called that simultaneously sets the coordinates and dimensions of the graphical object. And then properties of the graphical element are set. Next, the element is cropped to the size of its container.

Refine the event handler so that it can handle resizing an element for which permission for resizing by the mouse cursor is set. When handling the creation of new graphical objects, such an event should be handled only by container elements. Here, we will write the cursor coordinates to the resource manager:

//+------------------------------------------------------------------+
//| 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)
     {
      //--- Если это не элемент-контейнер - уходим
      if(this.Type()<ELEMENT_TYPE_PANEL)
         return;
      //--- Вызываем обработчик создания графического объекта
      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)
     {
      //--- Отправим координаты курсора в менеджер ресурсов
      CCommonManager::GetInstance().SetCursorX(x);
      CCommonManager::GetInstance().SetCursorY(y);

      //--- Неактивные элементы, кроме главного, не обрабатываем
      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();
            
               //--- Для изменяемых в размерах элементов
               if(this.m_resizable)
                 {
                  //--- Если не активирован режим изменения размеров,
                  //--- вызываем обработчик начала изменения размеров
                  if(!this.ResizeMode())
                     this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_BEGIN,x,y,this.NameFG());
                  //--- иначе, при активном режиме изменения размеров
                  //--- вызываем обработчик перетаскивания грани для изменения размеров
                  else
                     this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_DRAG,x,y,this.NameFG());
                 }
              }
           }
         //--- Курсор за пределами объекта
         else
           {
            //--- Если это активный главный объект, либо кнопка мышки зажата на графике, и это не режим изменения размеров - разрешаем инструменты графика
            if(this.IsMain() && (this.ActiveElementName()==this.NameFG() || this.ActiveElementName()=="Chart"))
               if(!this.ResizeMode())
                  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);
            
               //--- Для изменяемых в размерах элементов
               //--- вызываем обработчик перетаскивания грани для изменения размеров
               if(this.m_resizable)
                  this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_DRAG,x,y,this.NameFG());
              }
           }
        }
      
      //--- Кнопка мышки не нажата
      else
        {
         //--- Курсор в пределах объекта
         if(this.Contains(x, y))
           {
            //--- Если это главный элемент - отключаем инструменты графика
            if(this.IsMain())
               this.SetFlags(false);
            
            //--- Вызываем обработчик наведения курсора и
            //--- устанавливаем элемент как текущий активный
            this.OnFocusEvent(id,lparam,dparam,sparam);
            this.SetActiveElementName(this.NameFG());
            
            //--- Для изменяемых в размерах элементов
            //--- вызываем обработчик наведения курсора на облась изменения размеров
            if(this.m_resizable)
               this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_HOVER,x,y,this.NameFG());
           }
         
         //--- Курсор за пределами объекта
         else
           {
            //--- Если это главный объект
            if(this.IsMain())
              {
               //--- Разрешаем инструменты графика и
               //--- устанавливаем график как текущий активный элемент
               this.SetFlags(true);
               this.SetActiveElementName("Chart");
              }
            //--- Вызываем обработчик увода курсора из фокуса 
            this.OnReleaseEvent(id,lparam,dparam,sparam);
            
            //--- Для изменяемых в размерах элементов
            //--- вызываем обработчик режима не изменения размеров
            if(this.m_resizable)
               this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_NONE,x,y,this.NameFG());
           }
        }
     }
     
//--- Событие щелчка кнопкой мышки на объекте (отпускание кнопки)
   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(this.m_resizable)
           {
            //--- Отключаем режим изменения размеров, сбрасываем область взаимодействия,
            //--- вызываем обработчик завершения изменения размеров перетаскиванием граней
            this.SetResizeMode(false);
            this.SetResizeRegion(CURSOR_REGION_NONE);
            this.OnResizeZoneEvent(RESIZE_ZONE_ACTION_END,x,y,this.NameFG());
           }
        }
     }
   
//--- Событие прокрутки колёсика мышки
   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 handler calls the corresponding virtual resizing event handlers in various situations, and everything will be handled in them. We'll write these handlers later in controls classes.

We have finished refining base classes. Now open the Controls.mqh graphical element class file and make the necessary changes to it.

Since the controls can be resized manually, it is necessary to set limits on the minimum dimensions
The tooltip class will provide a feature to create different types of tooltips. To specify types of tooltips, write a special enumeration:

//+------------------------------------------------------------------+
//|                                                     Controls.mqh |
//|                                  Copyright 2025, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link      "https://www.mql5.com"

//+------------------------------------------------------------------+
//| Включаемые библиотеки                                            |
//+------------------------------------------------------------------+
#include "Base.mqh"

//+------------------------------------------------------------------+
//| Макроподстановки                                                 |
//+------------------------------------------------------------------+
#define  DEF_LABEL_W                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_PANEL_MIN_W            60          // Минимальная ширина панели
#define  DEF_PANEL_MIN_H            60          // Минимальная высота панели
#define  DEF_SCROLLBAR_TH           13          // Толщина полосы прокрутки по умолчанию
#define  DEF_THUMB_MIN_SIZE         8           // Минимальная толщина ползунка полосы прокрутки
#define  DEF_AUTOREPEAT_DELAY       500         // Задержка перед запуском автоповтора
#define  DEF_AUTOREPEAT_INTERVAL    100         // Частота автоповторов

//+------------------------------------------------------------------+
//| Перечисления                                                     |
//+------------------------------------------------------------------+
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,                       // Сравнение по группе элемента
  };
  
enum ENUM_HINT_TYPE                             // Типы подсказок
  {
   HINT_TYPE_TOOLTIP,                           // Тултип
   HINT_TYPE_ARROW_HORZ,                        // Двойная горизонтальная стрелка
   HINT_TYPE_ARROW_VERT,                        // Двойная вертикальная стрелка
   HINT_TYPE_ARROW_NWSE,                        // Двойная стрелка сверху-лево --- низ-право (NorthWest-SouthEast)
   HINT_TYPE_ARROW_NESW,                        // Двойная стрелка снизу-лево --- верх-право (NorthEast-SouthWest)
  };


Tooltip Class

The tooltip objects class will draw various arrows to indicate the direction of dragging the element boundaries to resize them. There is a special CImagePainter class for drawing various images.

Add (declare) methods for drawing tooltip arrows to it:

//--- Очищает область
   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) горизонтальную 17х7, (2) вертикальную 7х17 двойную стрелку
   bool              ArrowHorz(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); 
   bool              ArrowVert(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true); 
   
//--- Рисует диагональную (1) сверху-слева --- вниз-вправо, (2) снизу-слева --- вверх-вправо 17х17 двойную стрелку
   bool              ArrowNWSE(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true);
   bool              ArrowNESW(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);

Outside of the class body, write the implementation of the declared new methods:

//+------------------------------------------------------------------+
//| CImagePainter::Рисует горизонтальную 17х7 двойную стрелку        |
//+------------------------------------------------------------------+
bool CImagePainter::ArrowHorz(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;
   
//--- Координаты фигуры
   int arrx[15]={0, 3, 4, 4, 12, 12, 13, 16, 13, 12, 12, 4, 4, 3, 0};
   int arry[15]={3, 0, 0, 2,  2,  0,  0,  3,  6,  6,  4, 4, 6, 6, 3};
   
//--- Рисуем белую подложку
   this.m_canvas.Polyline(arrx,arry,::ColorToARGB(clrWhite,alpha));

//--- Рисуем линию стрелок
   this.m_canvas.Line(1,3, 15,3,::ColorToARGB(clr,alpha));
//--- Рисуем левый треугольник
   this.m_canvas.Line(1,3, 1,3,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(2,2, 2,4,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(3,1, 3,5,::ColorToARGB(clr,alpha));
//--- Рисуем правый треугольник
   this.m_canvas.Line(13,1, 13,5,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(14,2, 14,4,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(15,3, 15,3,::ColorToARGB(clr,alpha));
   
   if(update)
      this.m_canvas.Update(false);
   return true;
  }
//+------------------------------------------------------------------+
//| CImagePainter::Рисует вертикальную 7х17 двойную стрелку          |
//+------------------------------------------------------------------+
bool CImagePainter::ArrowVert(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Координаты фигуры
   int arrx[15]={3, 6, 6, 4,  4,  6,  6,  3,  0,  0,  2, 2, 0, 0, 3};
   int arry[15]={0, 3, 4, 4, 12, 12, 13, 16, 13, 12, 12, 4, 4, 3, 0};
   
//--- Рисуем белую подложку
   this.m_canvas.Polyline(arrx,arry,::ColorToARGB(clrWhite,alpha));

//--- Рисуем линию стрелок
   this.m_canvas.Line(3,1, 3,15,::ColorToARGB(clr,alpha));
//--- Рисуем верхний треугольник
   this.m_canvas.Line(3,1, 3,1,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(2,2, 4,2,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(1,3, 5,3,::ColorToARGB(clr,alpha));
//--- Рисуем нижний треугольник
   this.m_canvas.Line(1,13, 5,13,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(2,14, 4,14,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(3,15, 3,15,::ColorToARGB(clr,alpha));

   if(update)
      this.m_canvas.Update(false);
   return true;
  }
//+------------------------------------------------------------------+
//| CImagePainter::Рисует диагональную сверху-слева --- вниз-вправо  |
//| 13х13 двойную стрелку (NorthWest-SouthEast)                      |
//+------------------------------------------------------------------+
bool CImagePainter::ArrowNWSE(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Координаты фигуры
   int arrx[19]={0, 4, 5, 4, 4, 9, 10, 11, 12, 12,  8,  7,  8, 8, 3, 2, 1, 0, 0};
   int arry[19]={0, 0, 1, 2, 3, 8,  8,  7,  8, 12, 12, 11, 10, 9, 4, 4, 5, 4, 0};
   
//--- Рисуем белую подложку
   this.m_canvas.Polyline(arrx,arry,::ColorToARGB(clrWhite,alpha));

//--- Рисуем линию стрелок
   this.m_canvas.Line(3,3, 9,9,::ColorToARGB(clr,alpha));
//--- Рисуем верхний-левый треугольник
   this.m_canvas.Line(1,1, 4,1,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(1,2, 3,2,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(1,3, 3,3,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(1,4, 1,4,::ColorToARGB(clr,alpha));
//--- Рисуем нижний-правый треугольник
   this.m_canvas.Line(11,8, 11, 8,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(9, 9, 11, 9,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(9,10, 11,10,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(8,11, 11,11,::ColorToARGB(clr,alpha));

   if(update)
      this.m_canvas.Update(false);
   return true;
  }
//+------------------------------------------------------------------+
//| CImagePainter::Рисует диагональную снизу-слева --- вверх-вправо  |
//| 13х13 двойную стрелку (NorthEast-SouthWest)                      |
//+------------------------------------------------------------------+
bool CImagePainter::ArrowNESW(const int x,const int y,const int w,const int h,const color clr,const uchar alpha,const bool update=true)
  {
//--- Если область изображения не валидна - возвращаем false
   if(!this.CheckBound())
      return false;

//--- Координаты фигуры
   int arrx[19]={ 0, 0, 1, 2, 3, 8, 8, 7, 8, 12, 12, 11, 10, 9, 4,  4,  5,  4,  0};
   int arry[19]={12, 8, 7, 8, 8, 3, 2, 1, 0,  0,  4,  5,  4, 4, 9, 10, 11, 12, 12};
   
//--- Рисуем белую подложку
   this.m_canvas.Polyline(arrx,arry,::ColorToARGB(clrWhite,alpha));

//--- Рисуем линию стрелок
   this.m_canvas.Line(3,9, 9,3,::ColorToARGB(clr,alpha));
//--- Рисуем нижний-левый треугольник
   this.m_canvas.Line(1, 8, 1,8, ::ColorToARGB(clr,alpha));
   this.m_canvas.Line(1, 9, 3,9, ::ColorToARGB(clr,alpha));
   this.m_canvas.Line(1,10, 3,10,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(1,11, 4,11,::ColorToARGB(clr,alpha));
//--- Рисуем верхний-правый треугольник
   this.m_canvas.Line(8, 1, 11,1,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(9, 2, 11,2,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(9, 3, 11,3,::ColorToARGB(clr,alpha));
   this.m_canvas.Line(11,4, 11,4,::ColorToARGB(clr,alpha));

   if(update)
      this.m_canvas.Update(false);
   return true;
  }

At the specified coordinates, a white underlay is drawn first, and then a bidirectional arrow is drawn on top of it.

Now, implement a class of tooltip objects:

//+------------------------------------------------------------------+
//| Класс подсказки                                                  |
//+------------------------------------------------------------------+
class CVisualHint : public CButton
  {
protected:
   ENUM_HINT_TYPE    m_hint_type;                              // Тип подсказки

//--- Рисует (1) тултип, (2) горизонтальную, (3) вертикальную стрелку,
//--- стрелки (4) сверху-лево --- низ-право, (5) снизу-лево --- верх-право
   void              DrawTooltip(void);
   void              DrawArrHorz(void);
   void              DrawArrVert(void);
   void              DrawArrNWSE(void);
   void              DrawArrNESW(void);
   
//--- Инициализация цветов для типа подсказки (1) Tooltip, (2) стрелки
   void              InitColorsTooltip(void);
   void              InitColorsArrowed(void);
   
public:
//--- (1) Устанавливает, (2) возвращает тип подсказки
   void              SetHintType(const ENUM_HINT_TYPE type);
   ENUM_HINT_TYPE    HintType(void)                      const { return this.m_hint_type;             }

//--- Рисует внешний вид
   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_HINT);           }
   
//--- Инициализация (1) объекта класса, (2) цветов объекта по умолчанию
   void              Init(const string text);
   virtual void      InitColors(void);
   
//--- Конструкторы/деструктор
                     CVisualHint(void);
                     CVisualHint(const string object_name, const long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CVisualHint (void) {}


  };

Consider the methods declared in the class.

Class Constructors:

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

The parameters passed to the constructor are set in the object of the parent class, and the object initialization method is called.

Class Object Initialization Method:

//+------------------------------------------------------------------+
//| CVisualHint::Инициализация                                       |
//+------------------------------------------------------------------+
void CVisualHint::Init(const string text)
  {
//--- Инициализируем цвета по умолчанию
   this.InitColors();
//--- Устанавливаем смещение и размеры области изображенеия
   this.SetImageBound(0,0,this.Width(),this.Height());

//--- Объект не обрезается по границам контейнера
   this.m_trim_flag=false;
   
//--- Инициализируем счётчики автоповтора
   this.m_autorepeat_flag=true;

//--- Инициализируем свойства объекта управления автоповтором событий
   this.m_autorepeat.SetChartID(this.m_chart_id);
   this.m_autorepeat.SetID(0);
   this.m_autorepeat.SetName("VisualHintAutorepeatControl");
   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());
  }

Here, a flag is set for the object that prohibits cropping it along the container boundaries. All the tooltips are stored in the list of tooltips of each of the UI elements. The objects themselves are initially hidden and should be displayed only against events of cursor interaction with element boundaries. If the container size crop flag is set, arrow tooltips will always be hidden, since their location is always outside the element.

Regarding the tooltip type, it will always be cropped along the boundaries of its container, which is wrong, since the tooltip can be either completely within the element or go beyond it, either partially or completely. For it, it is also necessary to reset the crop flag along the container boundaries.

The Color Initialization Method For the Tooltip Type Hints:

//+------------------------------------------------------------------+
//| CVisualHint::Инициализация цветов для типа подсказки Tooltip     |
//+------------------------------------------------------------------+
void CVisualHint::InitColorsTooltip(void)
  {
//--- Фон и передний план непрозрачные
   this.SetAlpha(255);
   
//--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона
   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,clrLightGray);
   this.InitBorderColorsAct(clrLightGray,clrLightGray,clrLightGray,clrLightGray);
   this.BorderColorToDefault();
   
//--- Инициализируем цвет рамки и цвет переднего плана для заблокированного элемента
   this.InitBorderColorBlocked(clrNULL);
   this.InitForeColorBlocked(clrNULL);
  }

The Color Initialization Method For the Arrowed Type Hints:

//+------------------------------------------------------------------+
//| CVisualHint::Инициализация цветов для типа подсказки Arrowed     |
//+------------------------------------------------------------------+
void CVisualHint::InitColorsArrowed(void)
  {
//--- Фон прозрачный, передний план - непрозрачный
   this.SetAlphaBG(0);
   this.SetAlphaFG(255);
   
//--- Инициализируем цвета заднего плана для обычного и активированного состояний и делаем его текущим цветом фона
   this.InitBackColors(clrNULL,clrNULL,clrNULL,clrNULL);
   this.InitBackColorsAct(clrNULL,clrNULL,clrNULL,clrNULL);
   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(clrNULL);
  }

Each type of tooltips has its own background, foreground, and border colors. The default colors can be redefined at any time, and then the tooltips will use the newly set colors.

Default Object Color Initialization Method:

//+------------------------------------------------------------------+
//| CVisualHint::Инициализация цветов объекта по умолчанию           |
//+------------------------------------------------------------------+
void CVisualHint::InitColors(void)
  {
   if(this.m_hint_type==HINT_TYPE_TOOLTIP)
      this.InitColorsTooltip();
   else
      this.InitColorsArrowed();
  }

For each of the tooltip types, the corresponding default color initialization method is called.

A Method That Sets the Tooltip Type:

//+------------------------------------------------------------------+
//| CVisualHint::Устанавливает тип подсказки                         |
//+------------------------------------------------------------------+
void CVisualHint::SetHintType(const ENUM_HINT_TYPE type)
  {
//--- Если переданный тип соответствует установленному - уходим
   if(this.m_hint_type==type)
      return;
//--- Устанавливаем новый тип подсказки
   this.m_hint_type=type;
//--- В зависимости от типа подсказки устанавливаем размеры объекта
   switch(this.m_hint_type)
     {
      case HINT_TYPE_ARROW_HORZ  :  this.Resize(17,7);   break;
      case HINT_TYPE_ARROW_VERT  :  this.Resize(7,17);   break;
      case HINT_TYPE_ARROW_NESW  :
      case HINT_TYPE_ARROW_NWSE  :  this.Resize(13,13);  break;
      default                    :  break;
     }
//--- Устанавливаем смещение и размеры области изображенеия,
//--- инициализируем цвета по типу подсказки
   this.SetImageBound(0,0,this.Width(),this.Height());
   this.InitColors();
  }

One object can have five types of tooltips: tooltip and four bidirectional arrows. The method sets the specified type, resizes the object, and initializes object's colors according to the set hint type.

A Method That Draws the Appearance:

//+------------------------------------------------------------------+
//| CVisualHint::Рисует внешний вид                                  |
//+------------------------------------------------------------------+
void CVisualHint::Draw(const bool chart_redraw)
  {
//--- В зависимости от типа подсказки вызываем соответствующий метод рисования
   switch(this.m_hint_type)
     {
      case HINT_TYPE_ARROW_HORZ  :  this.DrawArrHorz(); break;
      case HINT_TYPE_ARROW_VERT  :  this.DrawArrVert(); break;
      case HINT_TYPE_ARROW_NESW  :  this.DrawArrNESW(); break;
      case HINT_TYPE_ARROW_NWSE  :  this.DrawArrNWSE(); break;
      default                    :  this.DrawTooltip(); break;
     }

//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

Depending on the set tooltip type, the corresponding drawing method is called.

Methods for Drawing Different Types of Tooltips:

//+------------------------------------------------------------------+
//| CVisualHint::Рисует тултип                                       |
//+------------------------------------------------------------------+
void CVisualHint::DrawTooltip(void)
  {
//--- Заливаем объект цветом фона, рисуем рамку и обновляем канвас фона
   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);
  }
//+------------------------------------------------------------------+
//| CVisualHint::Рисует горизонтальную стрелку                       |
//+------------------------------------------------------------------+
void CVisualHint::DrawArrHorz(void)
  {
//--- Очищаем область рисунка
   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);
//--- Рисуем двойную горизонтальную стрелку
   this.m_painter.ArrowHorz(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);
  }
//+------------------------------------------------------------------+
//| CVisualHint::Рисует вертикальную стрелку                         |
//+------------------------------------------------------------------+
void CVisualHint::DrawArrVert(void)
  {
//--- Очищаем область рисунка
   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);
//--- Рисуем двойную вертикальную стрелку
   this.m_painter.ArrowVert(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);
  }
//+------------------------------------------------------------------+
//| CVisualHint::Рисует стрелки сверху-лево --- низ-право            |
//+------------------------------------------------------------------+
void CVisualHint::DrawArrNWSE(void)
  {
//--- Очищаем область рисунка
   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);
//--- Рисуем двойную диагональную стрелку сверху-лево --- вниз-право
   this.m_painter.ArrowNWSE(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);
  }
//+------------------------------------------------------------------+
//| CVisualHint::Рисует стрелки снизу-лево --- верх-право            |
//+------------------------------------------------------------------+
void CVisualHint::DrawArrNESW(void)
  {
//--- Очищаем область рисунка
   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);
//--- Рисуем двойную диагональную стрелку снизу-лево --- верх-право
   this.m_painter.ArrowNESW(this.AdjX(this.m_painter.X()),this.AdjY(this.m_painter.Y()),this.m_painter.Width(),this.m_painter.Height(),this.ForeColor(),this.AlphaFG(),true);
  }

Only methods that draw arrow tooltips are fully implemented. To have a hint with the Tooltip type, you should refine the drawing method and implement a method that displays the specified text on the background canvas.


Refining Controls

In the CListObj object list class, in the element creation method, add a tooltip object:

//+------------------------------------------------------------------+
//| Метод создания элемента списка                                   |
//+------------------------------------------------------------------+
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_HINT              :  return new CVisualHint();        // Подсказка
      case ELEMENT_TYPE_LABEL             :  return new CLabel();             // Текстовая метка
      case ELEMENT_TYPE_BUTTON            :  return new CButton();            // Простая кнопка
      case ELEMENT_TYPE_BUTTON_TRIGGERED  :  return new CButtonTriggered();   // Двухпозиционная кнопка
      case ELEMENT_TYPE_BUTTON_ARROW_UP   :  return new CButtonArrowUp();     // Кнопка со стрелкой вверх
      case ELEMENT_TYPE_BUTTON_ARROW_DOWN :  return new CButtonArrowDown();   // Кнопка со стрелкой вниз
      case ELEMENT_TYPE_BUTTON_ARROW_LEFT :  return new CButtonArrowLeft();   // Кнопка со стрелкой влево
      case ELEMENT_TYPE_BUTTON_ARROW_RIGHT:  return new CButtonArrowRight();  // Кнопка со стрелкой вправо
      case ELEMENT_TYPE_CHECKBOX          :  return new CCheckBox();          // Элемент управления CheckBox
      case ELEMENT_TYPE_RADIOBUTTON       :  return new CRadioButton();       // Элемент управления RadioButton
      case ELEMENT_TYPE_PANEL             :  return new CPanel();             // Элемент управления Panel
      case ELEMENT_TYPE_GROUPBOX          :  return new CGroupBox();          // Элемент управления GroupBox
      case ELEMENT_TYPE_CONTAINER         :  return new CContainer();         // Элемент управления GroupBox
      default                             :  return NULL;
     }
  }

Add (declare) new variables and methods to the base class of the graphical element:

//+------------------------------------------------------------------+
//| Базовый класс графического элемента                              |
//+------------------------------------------------------------------+
class CElementBase : public CCanvasBase
  {
protected:
   CImagePainter     m_painter;                                // Класс рисования
   CListObj          m_list_hints;                             // Список подсказок
   int               m_group;                                  // Группа элементов
   bool              m_visible_in_container;                   // Флаг видимости в контейнере

//--- Добавляет указанный объект-подсказку в список
   bool              AddHintToList(CVisualHint *obj);
//--- Создаёт и добавляет новый объект-подсказку в список
   CVisualHint      *CreateAndAddNewHint(const ENUM_HINT_TYPE type, const string user_name, const int w, const int h);
//--- Добавляет существующий объект-подсказку в список
   CVisualHint      *AddHint(CVisualHint *obj, const int dx, const int dy);
//--- (1) Добавляет в список, (2) удаляет из списка объекты-подсказки со стрелками
   bool              AddHintsArrowed(void);
   bool              DeleteHintsArrowed(void);
//--- Отображает курсор изменения размеров
   bool              ShowCursorHint(const ENUM_CURSOR_REGION edge,int x,int y);
   
//--- Обработчик перетаскивания граней и углов элемента
   virtual void      ResizeActionDragHandler(const int x, const int y);
   
//--- Обработчики изменения размеров элемента по сторонам и углам
   virtual bool      ResizeZoneLeftHandler(const int x, const int y);
   virtual bool      ResizeZoneRightHandler(const int x, const int y);
   virtual bool      ResizeZoneTopHandler(const int x, const int y);
   virtual bool      ResizeZoneBottomHandler(const int x, const int y);
   virtual bool      ResizeZoneLeftTopHandler(const int x, const int y);
   virtual bool      ResizeZoneRightTopHandler(const int x, const int y);
   virtual bool      ResizeZoneLeftBottomHandler(const int x, const int y);
   virtual bool      ResizeZoneRightBottomHandler(const int x, const int y);
     
//--- Возвращает указатель на подсказку по (1) индексу, (2) идентификатору, (3) наименованию
   CVisualHint      *GetHintAt(const int index);
   CVisualHint      *GetHint(const int id);
   CVisualHint      *GetHint(const string name);

//--- Создаёт новую подсказку
   CVisualHint      *CreateNewHint(const ENUM_HINT_TYPE type, const string object_name, const string user_name, const int id, const int x, const int y, const int w, const int h);
//--- (1) Отображает указанную подсказку со стрелками, (2) скрывает все подсказки
   void              ShowHintArrowed(const ENUM_HINT_TYPE type,const int x,const int y);
   void              HideHintsAll(const bool chart_redraw);

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

//--- Создаёт и добавляет (1) новый, (2) ранее созданный объект-подсказку (только тултип) в список
   CVisualHint      *InsertNewTooltip(const ENUM_HINT_TYPE type, const string user_name, const int w, const int h);
   CVisualHint      *InsertTooltip(CVisualHint *obj, const int dx, const int dy);

//--- (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 void      SetResizable(const bool flag);
   
//--- (1) Устанавливает, (2) возвращает флаг видимости в контейнере
   virtual void      SetVisibleInContainer(const bool flag)    { this.m_visible_in_container=flag; }
   bool              IsVisibleInContainer(void)          const { return this.m_visible_in_container;}

//--- Возвращает описание объекта
   virtual string    Description(void);
   
//--- Обработчик изменения размеров (Resize)
   virtual void      OnResizeZoneEvent(const int id, const long lparam, const double dparam, const string sparam);
   
//--- Виртуальные методы (1) сравнения, (2) сохранения в файл, (3) загрузки из файла, (4) тип объекта
   virtual int       Compare(const CObject *node,const int mode=0) const;
   virtual bool      Save(const int file_handle);
   virtual bool      Load(const int file_handle);
   virtual int       Type(void)                          const { return(ELEMENT_TYPE_ELEMENT_BASE);}

//--- Конструкторы/деструктор
                     CElementBase(void) { this.m_painter.CanvasAssign(this.GetForeground()); this.m_visible_in_container=true; }
                     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) {}
  };

All the tooltip objects being added to the element will be located in the list m_list_hints. The m_visible_in_container flag sets the visibility of the element in the container. When the flag is set, the visibility of the element is controlled by the Show() and Hide() methods of the container. When the flag is reset, the programmer controls the element visibility.

For example, if container scrollbars are hidden (the container contents fit completely within the visible area), and if the container is hidden, then when the container's Show() method is called, the scrollbars will also be displayed if this flag is set for them. It shouldn't be like this. Therefore, for scrollbars, the m_visible_in_container flag is reset, and scrollbars are displayed according to the internal logic of the container — only if the container contents do not fit into the visible area and need to be scrolled.

In class constructors set the flag for element visibility in the container:

//--- Конструкторы/деструктор
                     CElementBase(void) { this.m_painter.CanvasAssign(this.GetForeground()); this.m_visible_in_container=true; }
                     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) {}
  };
//+-----------------------------------------------------------------------+
//| 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);
   this.m_visible_in_container=true;
  }

A Method That Sets the Resizability Flag:

//+------------------------------------------------------------------+
//| CElementBase::Устанавливает флаг возможности изменения размеров  |
//+------------------------------------------------------------------+
void CElementBase::SetResizable(const bool flag)
  {
//--- В родительский объект записываем флаг
   CCanvasBase::SetResizable(flag);
//--- Если флаг передан как true - создаём для курсора четыре подсказки со стрелками,
   if(flag)
      this.AddHintsArrowed();
//--- иначе - удаляем подсказки со стрелками для курсора
   else
      this.DeleteHintsArrowed();
  }

Set the specified flag value to the object. If the flag is passed as true, create four arrow tooltips for the element. If the flag is passed as false , delete earlier created arrow tooltips.

Methods That Return Pointers to Hints:

//+------------------------------------------------------------------+
//| CElementBase::Возвращает указатель на подсказку по индексу       |
//+------------------------------------------------------------------+
CVisualHint *CElementBase::GetHintAt(const int index)
  {
   return this.m_list_hints.GetNodeAtIndex(index);
  }
//+------------------------------------------------------------------+
//| CElementBase::Возвращает указатель на подсказку по идентификатору|
//+------------------------------------------------------------------+
CVisualHint *CElementBase::GetHint(const int id)
  {
   int total=this.m_list_hints.Total();
   for(int i=0;i<total;i++)
     {
      CVisualHint *obj=this.GetHintAt(i);
      if(obj!=NULL && obj.ID()==id)
         return obj;
     }
   return NULL;
  }
//+------------------------------------------------------------------+
//|CElementBase:: Возвращает указатель на подсказку по наименованию  |
//+------------------------------------------------------------------+
CVisualHint *CElementBase::GetHint(const string name)
  {
   int total=this.m_list_hints.Total();
   for(int i=0;i<total;i++)
     {
      CVisualHint *obj=this.GetHintAt(i);
      if(obj!=NULL && obj.Name()==name)
         return obj;
     }
   return NULL;
  }

A tooltip object with the specified property value is searched in the list and, if successful, a pointer to the found object is returned.

A Method That Adds a Specified Tooltip Object to the List:

//+------------------------------------------------------------------+
//| CElementBase::Добавляет указанный объект-подсказку в список      |
//+------------------------------------------------------------------+
bool CElementBase::AddHintToList(CVisualHint *obj)
  {
//--- Если передан пустой указатель - сообщаем об этом и возвращаем false
   if(obj==NULL)
     {
      ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__);
      return false;
     }
//--- Устанавливаем списку флаг сортировки по идентификатору
   this.m_list_hints.Sort(ELEMENT_SORT_BY_ID);
//--- Если такого элемента нет в списке - возвращаем результат его добавления в список
   if(this.m_list_hints.Search(obj)==NULL)
      return(this.m_list_hints.Add(obj)>-1);
//--- Элемент с таким идентификатором уже есть в списке - возвращаем false
   return false;
  }

A pointer to the object to be placed in the list is passed to the method. Tooltip objects are tracked by their ID when added. This means that each such object must have its own unique identifier.

A Method That Creates a New Tooltip Object:

//+------------------------------------------------------------------+
//| CElementBase::Создаёт новую подсказку                            |
//+------------------------------------------------------------------+
CVisualHint *CElementBase::CreateNewHint(const ENUM_HINT_TYPE type,const string object_name,const string user_name,const int id, const int x,const int y,const int w,const int h)
  {
//--- Создаём новый объект-подсказку
   CVisualHint *obj=new CVisualHint(object_name,this.m_chart_id,this.m_wnd,x,y,w,h);
   if(obj==NULL)
     {
      ::PrintFormat("%s: Error: Failed to create Hint object",__FUNCTION__);
      return NULL;
     }
//--- Устанавливаем идентификатор, наименование и тип подсказки
   obj.SetID(id);
   obj.SetName(user_name);
   obj.SetHintType(type);
   
//--- Возвращаем указатель на созданный объект
   return obj;
  }

The method creates a new object and sets the user's name, ID, and tooltip type. It returns a pointer to the created object.

A Method That Implements and Adds a New Hint Object to the List:

//+------------------------------------------------------------------+
//| CElementBase::Создаёт и добавляет новый объект-подсказку в список|
//+------------------------------------------------------------------+
CVisualHint *CElementBase::CreateAndAddNewHint(const ENUM_HINT_TYPE type,const string user_name,const int w,const int h)
  {
//--- Создаём имя графического объекта
   int obj_total=this.m_list_hints.Total();
   string obj_name=this.NameFG()+"_HNT"+(string)obj_total;
   
//--- Рассчитываем координаты объекта ниже и правее правого нижнего угла элемента
   int x=this.Right()+1;
   int y=this.Bottom()+1;
   
//--- Создаём новый объект-подсказку
   CVisualHint *obj=this.CreateNewHint(type,obj_name,user_name,obj_total,x,y,w,h);
   
//--- Если новый объект не создан - возвращаем NULL
   if(obj==NULL)
      return NULL;

//--- Устанавливаем пределы изображения, контейнер и z-order
   obj.SetImageBound(0,0,this.Width(),this.Height());
   obj.SetContainerObj(&this);
   obj.ObjectSetZOrder(this.ObjectZOrder()+1);

//--- Если созданный элемент не добавлен в список - сообщаем об этом, удаляем созданный элемент и возвращаем NULL
   if(!this.AddHintToList(obj))
     {
      ::PrintFormat("%s: Error. Failed to add Hint object with ID %d to list",__FUNCTION__,obj.ID());
      delete obj;
      return NULL;
     }
     
//--- Возвращаем указатель на созданный и присоединённый объект
   return obj;
  }

The main method for creating tooltips and placing them in the list of element hints.

A Method That Adds an Exhisting Tooltip Object to the List:

//+------------------------------------------------------------------+
//| CElementBase::Добавляет существующий объект-подсказку в список   |
//+------------------------------------------------------------------+
CVisualHint *CElementBase::AddHint(CVisualHint *obj,const int dx,const int dy)
  {
//--- Если передан объект не с типом подсказки - возвращаем NULL
   if(obj.Type()!=ELEMENT_TYPE_HINT)
     {
      ::PrintFormat("%s: Error. Only an object with the Hint type can be used here. The element type \"%s\" was passed",__FUNCTION__,ElementDescription((ENUM_ELEMENT_TYPE)obj.Type()));
      return NULL;
     }
//--- Запоминаем идентификатор объекта и устанавливаем новый
   int id=obj.ID();
   obj.SetID(this.m_list_hints.Total());
   
//--- Добавляем объект в список; при неудаче - сообщаем об этом, устанавливаем начальный идентификатор и возвращаем NULL
   if(!this.AddHintToList(obj))
     {
      ::PrintFormat("%s: Error. Failed to add Hint object to list",__FUNCTION__);
      obj.SetID(id);
      return NULL;
     }
//--- Устанавливаем новые координаты, контейнер и z-order объекта
   int x=this.X()+dx;
   int y=this.Y()+dy;
   obj.Move(x,y);
   obj.SetContainerObj(&this);
   obj.ObjectSetZOrder(this.ObjectZOrder()+1);
     
//--- Возвращаем указатель на присоединённый объект
   return obj;
  }

This method allows you to add a previously created tooltip object to the list of element hints.

A Method That Adds Arrow Hint Objects To the Llist:

//+------------------------------------------------------------------+
//| CElementBase::Добавляет в список объекты-подсказки со стрелками  |
//+------------------------------------------------------------------+
bool CElementBase::AddHintsArrowed(void)
  {
//--- Массивы наименований и типов подсказок
   string array[4]={"HintHORZ","HintVERT","HintNWSE","HintNESW"};
   ENUM_HINT_TYPE type[4]={HINT_TYPE_ARROW_HORZ,HINT_TYPE_ARROW_VERT,HINT_TYPE_ARROW_NWSE,HINT_TYPE_ARROW_NESW};
   
//--- В цикле создаём четыре подсказки со стрелками
   bool res=true;
   for(int i=0;i<(int)array.Size();i++)
      res &=(this.CreateAndAddNewHint(type[i],array[i],0,0)!=NULL);
      
//--- Если были ошибки при создании - возвращаем false
   if(!res)
      return false;
      
//--- В цикле по массиву наименований объектов-подсказок
   for(int i=0;i<(int)array.Size();i++)
     {
      //--- получаем очередной объект по наименованию,
      CVisualHint *obj=this.GetHint(array[i]);
      if(obj==NULL)
         continue;
      //--- скрываем объект и рисуем внешний вид (стрелки в соответствии с типом объекта)
      obj.Hide(false);
      obj.Draw(false);
     }
//--- Всё успешно
   return true;
  }

The method sequentially creates and adds all four types of arrow tooltips to the list of element tooltips.

A Method That Removes All Arrow Tooltip Objects From the List:

//+------------------------------------------------------------------+
//| CElementBase::Удаляет из списка объекты-подсказки со стрелками   |
//+------------------------------------------------------------------+
bool CElementBase::DeleteHintsArrowed(void)
  {
//--- В цикле по списку объектов-подсказок
   bool res=true;
   for(int i=this.m_list_hints.Total()-1;i>=0;i--)
     {
      //--- получаем очередной объект и, если это не тултип - удаляем его
      CVisualHint *obj=this.m_list_hints.GetNodeAtIndex(i);
      if(obj!=NULL && obj.HintType()!=HINT_TYPE_TOOLTIP)
         res &=this.m_list_hints.DeleteCurrent();
     }
//--- Возвращаем результат удаления подсказок со стрелками
   return res;
  }

In the loop, we search through the list of tooltips for objects with a non-Tooltip hint type and delete each one from the list.

A Method That Implements and Adds a New Tooltip Object with Tooltip Type to the List:

//+------------------------------------------------------------------+
//| CElementBase::Создаёт и добавляет новый объект-подсказку в список|
//+------------------------------------------------------------------+
CVisualHint *CElementBase::InsertNewTooltip(const ENUM_HINT_TYPE type,const string user_name,const int w,const int h)
  {
//--- Если тип подсказки не тултип - сообщаем об этом и возвращаем NULL
   if(type!=HINT_TYPE_TOOLTIP)
     {
      ::PrintFormat("%s: Error. Only a tooltip can be added to an element",__FUNCTION__);
      return NULL;
     }
//--- Создаём и добавляем новый объект-подсказку в список;
//--- Возвращаем указатель на созданный и присоединённый объект
   return this.CreateAndAddNewHint(type,user_name,w,h);
  }

A Method That Adds an Earlier Created Hint Object to the List:

//+------------------------------------------------------------------+
//| CElementBase::Добавляет ранее созданный объект-подсказку в список|
//+------------------------------------------------------------------+
CVisualHint *CElementBase::InsertTooltip(CVisualHint *obj,const int dx,const int dy)
  {
//--- Если передан пустой или невалидный указатель на объект - возвращаем NULL
   if(::CheckPointer(obj)==POINTER_INVALID)
     {
      ::PrintFormat("%s: Error. Empty element passed",__FUNCTION__);
      return NULL;
     }
//--- Если тип подсказки не тиултип - сообщаем об этом и возвращаем NULL
   if(obj.HintType()!=HINT_TYPE_TOOLTIP)
     {
      ::PrintFormat("%s: Error. Only a tooltip can be added to an element",__FUNCTION__);
      return NULL;
     }
//--- Добавляем указанный объект-подсказку в список;
//--- Возвращаем указатель на созданный и присоединённый объект
   return this.AddHint(obj,dx,dy);
  }

These methods allow adding either a new or an existing Tooltip to the list of element hints. It is useful if the element dynamically displays areas where tooltips should appear when hovering over them.

A Method That Displays the Indicated Tooltip  In the Specified Coordinates:

//+------------------------------------------------------------------+
//| CElementBase::Отображает указанную подсказку                     |
//| в указанных координатах                                          |
//+------------------------------------------------------------------+
void CElementBase::ShowHintArrowed(const ENUM_HINT_TYPE type,const int x,const int y)
  {
   CVisualHint *hint=NULL; // Указатель на искомый объект
//--- В цикле по списку объектов подсказок
   for(int i=0;i<this.m_list_hints.Total();i++)
     {
      //--- получаем указатель на очередной объект
      CVisualHint *obj=this.GetHintAt(i);
      if(obj==NULL)
         continue;
      //--- Если это искомый тип подсказки - запоминаем указатель,
      if(obj.HintType()==type)
         hint=obj;
      //--- иначе - скрываем объект
      else
         obj.Hide(false);
     }
//--- Если искомый объект найден и он скрыт
   if(hint!=NULL && hint.IsHidden())
     {
      //--- помещаем объект в указанные координаты,
      //--- рисуем внешний вид и переносим объект на передний план, делая его видимым
      hint.Move(x,y);
      hint.Draw(false);
      hint.BringToTop(true);
     }
  }

The method searches for a tooltip with the indicated type and displays it in the coordinates indicated in method’s formal parameters. It displays the first counter tooltip of the specified type. All other tooltips are hidden. The method is designed to display arrow tooltips, of which there should be four objects in the list. First of all, all the tooltips are hidden in the loop, and only then the desired one is displayed.

A Method That Hides All Tooltips:

//+------------------------------------------------------------------+
//| CElementBase::Скрывает все подсказки                             |
//+------------------------------------------------------------------+
void CElementBase::HideHintsAll(const bool chart_redraw)
  {
//--- В цикле по списку объектов-подсказок
   for(int i=0;i<this.m_list_hints.Total();i++)
     {
      //--- получаем очередной объект и скрываем его
      CVisualHint *obj=this.GetHintAt(i);
      if(obj!=NULL)
         obj.Hide(false);
     }
//--- Если указано, перерисовываем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

In a loop through the list of tooltip objects, each regular object from the list is hidden.

A Method That Displays a Tooltip Next To the Cursor:

//+------------------------------------------------------------------+
//| CElementBase::Отображает курсор изменения размеров               |
//+------------------------------------------------------------------+
bool CElementBase::ShowCursorHint(const ENUM_CURSOR_REGION edge,int x,int y)
  {
   CVisualHint *hint=NULL;          // Указатель на подсказку
   int hint_shift_x=0;              // Смещение подсказки по X
   int hint_shift_y=0;              // Смещение подсказки по Y
   
//--- В зависимости от расположения курсора на границах элемента
//--- указываем смещения подсказки относительно координат курсора,
//--- отображаем на графике требуемую подсказку и получаем указатель на этот объект
   switch(edge)
     {
      //--- Курсор на правой или левой границе - горизонтальная двойная стрелка
      case CURSOR_REGION_RIGHT         :
      case CURSOR_REGION_LEFT          :
         hint_shift_x=1;
         hint_shift_y=18;
         this.ShowHintArrowed(HINT_TYPE_ARROW_HORZ,x+hint_shift_x,y+hint_shift_y);
         hint=this.GetHint("HintHORZ");
        break;
    
      //--- Курсор на верхней или нижней границе - вертикальная двойная стрелка
      case CURSOR_REGION_TOP           :
      case CURSOR_REGION_BOTTOM        :
         hint_shift_x=12;
         hint_shift_y=4;
         this.ShowHintArrowed(HINT_TYPE_ARROW_VERT,x+hint_shift_x,y+hint_shift_y);
         hint=this.GetHint("HintVERT");
        break;
    
      //--- Курсор в левом верхнем или правом нижнем углу - диагональная двойная стрелка от лево-верх до право-низ
      case CURSOR_REGION_LEFT_TOP      :
      case CURSOR_REGION_RIGHT_BOTTOM  :
         hint_shift_x=10;
         hint_shift_y=2;
         this.ShowHintArrowed(HINT_TYPE_ARROW_NWSE,x+hint_shift_x,y+hint_shift_y);
         hint=this.GetHint("HintNWSE");
        break;
    
      //--- Курсор в левом нижнем или правом верхнем углу - диагональная двойная стрелка от лево-низ до право-верх
      case CURSOR_REGION_LEFT_BOTTOM   :
      case CURSOR_REGION_RIGHT_TOP     :
         hint_shift_x=5;
         hint_shift_y=12;
         this.ShowHintArrowed(HINT_TYPE_ARROW_NESW,x+hint_shift_x,y+hint_shift_y);
         hint=this.GetHint("HintNESW");
        break;
      
      //--- По умолчанию ничего не делаем
      default: break;
     }

//--- Возвращаем результат корректировки положения подсказки относительно курсора
   return(hint!=NULL ? hint.Move(x+hint_shift_x,y+hint_shift_y) : false);
  }

Depending on the edge of the element or its angle, the corresponding tooltip is displayed next to the cursor.

A Handler For Resizing:

//+------------------------------------------------------------------+
//| CElementBase::Обработчик изменения размеров                      |
//+------------------------------------------------------------------+
void CElementBase::OnResizeZoneEvent(const int id,const long lparam,const double dparam,const string sparam)
  {
   int x=(int)lparam;               // Координата X курсора
   int y=(int)dparam;               // Координата Y курсора
   int shift_x=0;                   // Смещение подсказки по X
   int shift_y=0;                   // Смещение подсказки по Y
   
//--- Получаем положение курсора относительно границ элемента и режим взаимодействия
   ENUM_CURSOR_REGION edge=(this.ResizeRegion()==CURSOR_REGION_NONE ? this.CheckResizeZone(x,y) : this.ResizeRegion());
   ENUM_RESIZE_ZONE_ACTION action=(ENUM_RESIZE_ZONE_ACTION)id;

//--- Если курсор за границами изменения размеров или только что наведён на зону взаимодействия
   if(action==RESIZE_ZONE_ACTION_NONE || (action==RESIZE_ZONE_ACTION_HOVER && edge==CURSOR_REGION_NONE))
     {
      //--- отключаем режим изменения размеров и регион взаимодействия,
      //--- скрываем все подсказки
      this.SetResizeMode(false);
      this.SetResizeRegion(CURSOR_REGION_NONE);
      this.HideHintsAll(true);
     }

//--- Курсор на одной из границ изменения размеров
   if(action==RESIZE_ZONE_ACTION_HOVER)
     {
      //--- Отображаем подсказку со стрелкой для региона взаимодействия
      if(this.ShowCursorHint(edge,x,y))
         ::ChartRedraw(this.m_chart_id);
     }
   
//--- Начало изменения размеров
   if(action==RESIZE_ZONE_ACTION_BEGIN)
     {
      //--- включаем режим изменения размеров и регион взаимодействия,
      //--- отображаем соответствующую подсказку курсора
      this.SetResizeMode(true);
      this.SetResizeRegion(edge);
      this.ShowCursorHint(edge,x,y);
     }
   
//--- Перетаскивание границы объекта для изменения размеров элемента
   if(action==RESIZE_ZONE_ACTION_DRAG)
     {
      //--- Вызываем обработчик перетягивания границ объекта для изменения его размеров,
      //--- отображаем соответствующую подсказку курсора
      this.ResizeActionDragHandler(x,y);
      this.ShowCursorHint(edge,x,y);
     }
  }

As an event identifier (id), the cursor action in the interaction zone is passed to the handler (pointed at the zone, moves with the button held down, the button is released). Next, we get the element boundary where the event occurs and handle it. The entire logic is described in the comments to the code and should not arise any questions, hopefully. All situations are handled by special handlers, discussed below.

A Handler For Dragging the Edges and Corners of an Element:

//+------------------------------------------------------------------+
//| CElementBase::Обработчик перетаскивания граней и углов элемента  |
//+------------------------------------------------------------------+
void CElementBase::ResizeActionDragHandler(const int x, const int y)
  {
//--- Изменение размера за правую границу
   if(this.ResizeRegion()==CURSOR_REGION_RIGHT)
      this.ResizeZoneRightHandler(x,y);
//--- Изменение размера за нижнюю границу
   if(this.ResizeRegion()==CURSOR_REGION_BOTTOM)
      this.ResizeZoneBottomHandler(x,y);
//--- Изменение размера за левую границу
   if(this.ResizeRegion()==CURSOR_REGION_LEFT)
      this.ResizeZoneLeftHandler(x,y);
//--- Изменение размера за верхнюю границу
   if(this.ResizeRegion()==CURSOR_REGION_TOP)
      this.ResizeZoneTopHandler(x,y);
//--- Изменение размера за правый нижний угол
   if(this.ResizeRegion()==CURSOR_REGION_RIGHT_BOTTOM)
      this.ResizeZoneRightBottomHandler(x,y);
//--- Изменение размера за правый верхний угол
   if(this.ResizeRegion()==CURSOR_REGION_RIGHT_TOP)
      this.ResizeZoneRightTopHandler(x,y);
//--- Изменение размера за левый нижний угол
   if(this.ResizeRegion()==CURSOR_REGION_LEFT_BOTTOM)
      this.ResizeZoneLeftBottomHandler(x,y);
//--- Изменение размера за левый верхний угол
   if(this.ResizeRegion()==CURSOR_REGION_LEFT_TOP)
      this.ResizeZoneLeftTopHandler(x,y);
  }

Depending on the element edge, or on its angle, with which interaction takes place, specialized handlers of these events are called:

//+------------------------------------------------------------------+
//| CElementBase::Обработчик изменения размеров за нижнюю грань      |
//+------------------------------------------------------------------+
bool CElementBase::ResizeZoneBottomHandler(const int x,const int y)
  {
//--- Рассчитываем и устанавливаем новую высоту элемента
   int height=::fmax(y-this.Y(),DEF_PANEL_MIN_H);
   if(!this.ResizeH(height))
      return false;
//--- Получаем указатель на подсказку
   CVisualHint *hint=this.GetHint("HintVERT");
   if(hint==NULL)
      return false;
//--- Смещаем подсказку на указанные величины относительно курсора
   int shift_x=12;
   int shift_y=4;
   return hint.Move(x+shift_x,y+shift_y);
  }
//+------------------------------------------------------------------+
//| CElementBase::Изменение размера за левую грань                   |
//+------------------------------------------------------------------+
bool CElementBase::ResizeZoneLeftHandler(const int x,const int y)
  {
//--- Рассчитываем новые координату X и ширину элемента
   int new_x=::fmin(x,this.Right()-DEF_PANEL_MIN_W+1);
   int width=this.Right()-new_x+1;
//--- Устанавливаем новые координату X и ширину элемента
   if(!this.MoveXYWidthResize(new_x,this.Y(),width,this.Height()))
      return false;
//--- Получаем указатель на подсказку
   CVisualHint *hint=this.GetHint("HintHORZ");
   if(hint==NULL)
      return false;
//--- Смещаем подсказку на указанные величины относительно курсора
   int shift_x=1;
   int shift_y=18;
   return hint.Move(x+shift_x,y+shift_y);
  }
//+------------------------------------------------------------------+
//| CElementBase::Изменение размера за верхнюю грань                 |
//+------------------------------------------------------------------+
bool CElementBase::ResizeZoneTopHandler(const int x,const int y)
  {
//--- Рассчитываем новые координату Y и высоту элемента
   int new_y=::fmin(y,this.Bottom()-DEF_PANEL_MIN_H+1);
   int height=this.Bottom()-new_y+1;
//--- Устанавливаем новые координату Y и высоту элемента
   if(!this.MoveXYWidthResize(this.X(),new_y,this.Width(),height))
      return false;
//--- Получаем указатель на подсказку
   CVisualHint *hint=this.GetHint("HintVERT");
   if(hint==NULL)
      return false;
//--- Смещаем подсказку на указанные величины относительно курсора
   int shift_x=12;
   int shift_y=4;
   return hint.Move(x+shift_x,y+shift_y);
  }
//+------------------------------------------------------------------+
//| CElementBase::Изменение размера за правый нижний угол            |
//+------------------------------------------------------------------+
bool CElementBase::ResizeZoneRightBottomHandler(const int x,const int y)
  {
//--- Рассчитываем и устанавливаем новые ширину и высоту элемента
   int width =::fmax(x-this.X()+1, DEF_PANEL_MIN_W);
   int height=::fmax(y-this.Y()+1, DEF_PANEL_MIN_H);
   if(!this.Resize(width,height))
      return false;
//--- Получаем указатель на подсказку
   CVisualHint *hint=this.GetHint("HintNWSE");
   if(hint==NULL)
      return false;
//--- Смещаем подсказку на указанные величины относительно курсора
   int shift_x=10;
   int shift_y=2;
   return hint.Move(x+shift_x,y+shift_y);
  }
//+------------------------------------------------------------------+
//| CElementBase::Изменение размера за правый верхний угол           |
//+------------------------------------------------------------------+
bool CElementBase::ResizeZoneRightTopHandler(const int x,const int y)
  {
//--- Рассчитываем и устанавливаем новые координату Y, ширину и высоту элемента
   int new_y=::fmin(y, this.Bottom()-DEF_PANEL_MIN_H+1);
   int width =::fmax(x-this.X()+1, DEF_PANEL_MIN_W);
   int height=this.Bottom()-new_y+1;
   if(!this.MoveXYWidthResize(this.X(),new_y,width,height))
      return false;
//--- Получаем указатель на подсказку
   CVisualHint *hint=this.GetHint("HintNESW");
   if(hint==NULL)
      return false;
//--- Смещаем подсказку на указанные величины относительно курсора
   int shift_x=5;
   int shift_y=12;
   return hint.Move(x+shift_x,y+shift_y);
  }
//+------------------------------------------------------------------+
//| CElementBase::Изменение размера за левый нижний угол             |
//+------------------------------------------------------------------+
bool CElementBase::ResizeZoneLeftBottomHandler(const int x,const int y)
  {
//--- Рассчитываем и устанавливаем новые координату X, ширину и высоту элемента
   int new_x=::fmin(x, this.Right()-DEF_PANEL_MIN_W+1);
   int width =this.Right()-new_x+1;
   int height=::fmax(y-this.Y()+1, DEF_PANEL_MIN_H);
   if(!this.MoveXYWidthResize(new_x,this.Y(),width,height))
      return false;
//--- Получаем указатель на подсказку
   CVisualHint *hint=this.GetHint("HintNESW");
   if(hint==NULL)
      return false;
//--- Смещаем подсказку на указанные величины относительно курсора
   int shift_x=5;
   int shift_y=12;
   return hint.Move(x+shift_x,y+shift_y);
  }
//+------------------------------------------------------------------+
//| CElementBase::Изменение размера за левый верхний угол            |
//+------------------------------------------------------------------+
bool CElementBase::ResizeZoneLeftTopHandler(const int x,const int y)
  {
//--- Рассчитываем и устанавливаем новые координаты X и Y, ширину и высоту элемента
   int new_x=::fmin(x,this.Right()-DEF_PANEL_MIN_W+1);
   int new_y=::fmin(y,this.Bottom()-DEF_PANEL_MIN_H+1);
   int width =this.Right() -new_x+1;
   int height=this.Bottom()-new_y+1;
   if(!this.MoveXYWidthResize(new_x, new_y,width,height))
      return false;
//--- Получаем указатель на подсказку
   CVisualHint *hint=this.GetHint("HintNWSE");
   if(hint==NULL)
      return false;
//--- Смещаем подсказку на указанные величины относительно курсора
   int shift_x=10;
   int shift_y=2;
   return hint.Move(x+shift_x,y+shift_y);
  }

The handlers calculate a new size of the element and, if necessary, its new coordinates. Its new dimensions (and coordinates) are set, and a tooltip with arrows near the cursor is displayed.

In the methods of working with files, add saving and loading a list of tooltips, and a visibility flag in the container:

//+------------------------------------------------------------------+
//| CElementBase::Сохранение в файл                                  |
//+------------------------------------------------------------------+
bool CElementBase::Save(const int file_handle)
  {
//--- Сохраняем данные родительского объекта
   if(!CCanvasBase::Save(file_handle))
      return false;
  
//--- Сохраняем список подсказок
   if(!this.m_list_hints.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;
//--- Сохраняем флаг видимости в контейнере
   if(::FileWriteInteger(file_handle,this.m_visible_in_container,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_list_hints.Load(file_handle))
      return false;      
//--- Загружаем объект изображения
   if(!this.m_painter.Load(file_handle))
      return false;
//--- Загружаем группу
   this.m_group=::FileReadInteger(file_handle,INT_VALUE);
//--- Загружаем флаг видимости в контейнере
   this.m_visible_in_container=::FileReadInteger(file_handle,INT_VALUE);
   
//--- Всё успешно
   return true;
  }

Containers (Panel, Group of Elements, Container) must have their own resizing methods. 

Just implement these virtual methods in the CPanel class and add a method that simultaneously changes the size and coordinates of the element:

//+------------------------------------------------------------------+
//| Класс панели                                                     |
//+------------------------------------------------------------------+
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 bool      ResizeW(const int w);
   virtual bool      ResizeH(const int h);
   virtual bool      Resize(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);
//--- Устанавливает одновременно координаты и размеры элемента
   virtual bool      MoveXYWidthResize(const int x,const int y,const int w,const int h);
   
//--- (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 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(); }
  };

Outside of the class body, write the implementation of panel resizing methods:

//+------------------------------------------------------------------+
//| CPanel::Изменяет ширину объекта                                  |
//+------------------------------------------------------------------+
bool CPanel::ResizeW(const int w)
  {
   if(!this.ObjectResizeW(w))
      return false;
   this.BoundResizeW(w);
   this.SetImageSize(w,this.Height());
   if(!this.ObjectTrim())
     {
      this.Update(false);
      this.Draw(false);
     }
   return true;
  }
//+------------------------------------------------------------------+
//| CPanel::Изменяет высоту объекта                                  |
//+------------------------------------------------------------------+
bool CPanel::ResizeH(const int h)
  {
   if(!this.ObjectResizeH(h))
      return false;
   this.BoundResizeH(h);
   this.SetImageSize(this.Width(),h);
   if(!this.ObjectTrim())
     {
      this.Update(false);
      this.Draw(false);
     }
   return true;
  }
//+------------------------------------------------------------------+
//| CPanel::Изменяет размеры объекта                                 |
//+------------------------------------------------------------------+
bool CPanel::Resize(const int w,const int h)
  {
   if(!this.ObjectResize(w,h))
      return false;
   this.BoundResize(w,h);
   this.SetImageSize(w,h);
   if(!this.ObjectTrim())
     {
      this.Update(false);
      this.Draw(false);
     }
   return true;
  }

First, dimensions of the graphic object are changed, then new dimensions of the element and the drawing area are set. Next, the element is cropped along boundaries of its container.

Scrollbars should be skipped in the method that draws the appearance, since other methods are responsible for their rendering:

//+------------------------------------------------------------------+
//| 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.Type()!=ELEMENT_TYPE_SCROLLBAR_H && elm.Type()!=ELEMENT_TYPE_SCROLLBAR_V)
         elm.Draw(false);
     }
//--- Если указано - обновляем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

A Method That Sets Panel’s Coordinates And Dimensions Simultaneously:

//+------------------------------------------------------------------+
//| CPanel::Устанавливает одновременно координаты и размеры элемента |
//+------------------------------------------------------------------+
bool CPanel::MoveXYWidthResize(const int x,const int y,const int w,const int h)
  {
   //--- Вычисляем дистанцию, на которую сместится элемент
   int delta_x=x-this.X();
   int delta_y=y-this.Y();

   //--- Перемещаем элемент на указанные координаты с изменением размеров
   if(!CCanvasBase::MoveXYWidthResize(x,y,w,h))
      return false;
   this.BoundMove(x,y);
   this.BoundResize(w,h);
   this.SetImageBound(0,0,this.Width(),this.Height());
   if(!this.ObjectTrim())
     {
      this.Update(false);
      this.Draw(false);
     }
   
//--- Перемещаем все привязанные элементы на рассчитанную дистанцию
   bool res=true;
   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 graphic object is shifted with a change in its size. Then new coordinates and dimensions of the panel are set, a new size of the image area is set, and the element is cropped along the bounds of its container. Then all the anchored elements are shifted by the offset distance of the panel.

In a method that displays an object on all chart periods, it is necessary to exclude the display of scrollbars and objects having a special visibility flag. Their visibility is controlled by methods of the container object class:

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

Similarly, in the method that places an object in the foreground, scrollbars must be skipped:

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

Scrollbars themselves must have a set flag that prohibits them from being cropped along the container boundaries. If this is not done, their visibility will be controlled by the ObjectTrim() method, which hides all objects that go beyond the boundaries of the container’s visible area. And it is in this area that scrollbars are located.

In the Init methods of both scrollbar objects, set the following flag:

//+------------------------------------------------------------------+
//| CScrollBarThumbH::Инициализация                                  |
//+------------------------------------------------------------------+
void CScrollBarThumbH::Init(const string text)
  {
//--- Инициализация родительского класса
   CButton::Init("");
//--- Устанавливаем флаги перемещаемости и обновления графика
   this.SetMovable(true);
   this.SetChartRedrawFlag(false);
//--- Элемент не обрезается по границам контейнера
   this.m_trim_flag=false;
  
//+------------------------------------------------------------------+
//| CScrollBarThumbV::Инициализация                                  |
//+------------------------------------------------------------------+
void CScrollBarThumbV::Init(const string text)
  {
//--- Инициализация родительского класса
   CButton::Init("");
//--- Устанавливаем флаги перемещаемости и обновления графика
   this.SetMovable(true);
   this.SetChartRedrawFlag(false);
//--- Элемент не обрезается по границам контейнера
   this.m_trim_flag=false;
  }

Add two methods to the horizontal scrollbar class — for setting the thumb position and for setting the visibility flag in the container:

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

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

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

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

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

//--- Рисует внешний вид
   virtual void      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) {}
  };

Outside of the class body, implement the method for setting the visibility flag in the container:

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

Here, a flag passed to the method is set for each component of the scrollbar.

In the initialization method, set flags for each component of the scrollbar:

//+------------------------------------------------------------------+
//| 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_left.SetTrimmered(false);
   this.m_butt_left.SetVisibleInContainer(false);
   
//--- Настраиваем цвета и вид кнопки со стрелкой вправо
   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();
   this.m_butt_right.SetTrimmered(false);
   this.m_butt_right.SetVisibleInContainer(false);
   
//--- Создаём ползунок
   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.SetTrimmered(false);
   this.m_thumb.SetVisibleInContainer(false);
//--- Запрещаем самостоятельную перерисовку графика
   this.m_thumb.SetChartRedrawFlag(false);
//--- Изначально в контейнере не отображается
   this.m_visible_in_container=false;
  }

We will make exactly the same improvements in the vertical scrollbar 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              SetThumbPosition(const int pos)     const { return(this.m_thumb!=NULL ? this.m_thumb.MoveY(pos) : false);         }
//--- Изменяет размер ползунка
   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      SetVisibleInContainer(const bool flag);

//--- Рисует внешний вид
   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::Инициализация                                       |
//+------------------------------------------------------------------+
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_up.SetTrimmered(false);
   this.m_butt_up.SetVisibleInContainer(false);
   
//--- Настраиваем цвета и вид кнопки со стрелкой вниз
   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.SetTrimmered(false);
   this.m_butt_down.SetVisibleInContainer(false);
   
//--- Создаём ползунок
   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.SetTrimmered(false);
   this.m_thumb.SetVisibleInContainer(false);
//--- запрещаем самостоятельную перерисовку графика
   this.m_thumb.SetChartRedrawFlag(false);
//--- Изначально в контейнере не отображается
   this.m_visible_in_container=false;
  }
//+------------------------------------------------------------------+
//| CScrollBarV::Устанавливает флаг видимости в контейнере           |
//+------------------------------------------------------------------+
void CScrollBarV::SetVisibleInContainer(const bool flag)
  {
   this.m_visible_in_container=flag;
   if(this.m_butt_up!=NULL)
      this.m_butt_up.SetVisibleInContainer(flag);
   if(this.m_butt_down!=NULL)
      this.m_butt_down.SetVisibleInContainer(flag);
   if(this.m_thumb!=NULL)
      this.m_thumb.SetVisibleInContainer(flag);
  }

In the class of the CContainer container object, declare new variables and methods:

//+------------------------------------------------------------------+
//| Класс Контейнер                                                  |
//+------------------------------------------------------------------+
class CContainer : public CPanel
  {
private:
   bool              m_visible_scrollbar_h;                    // Флаг видимости горизонтальной полосы прокрутки
   bool              m_visible_scrollbar_v;                    // Флаг видимости вертикальной полосы прокрутки
   int               m_init_border_size_top;                   // Изначальный размер рамки сверху
   int               m_init_border_size_bottom;                // Изначальный размер рамки снизу
   int               m_init_border_size_left;                  // Изначальный размер рамки слева
   int               m_init_border_size_right;                 // Изначальный размер рамки справа
   
//--- Возвращает тип элемента, отправившего событие
   ENUM_ELEMENT_TYPE GetEventElementType(const string name);
   
protected:
   CScrollBarH      *m_scrollbar_h;                            // Указатель на горизонтальную полосу прокрутки
   CScrollBarV      *m_scrollbar_v;                            // Указатель на вертикальную полосу прокрутки
 
//--- Обработчик перетаскивания граней и углов элемента
   virtual void      ResizeActionDragHandler(const int x, const int y);
   
//--- Проверяет размеры элемента для отображения полос прокрутки
   void              CheckElementSizes(CElementBase *element);
//--- Рассчитывает и возвращает размер (1) ползунка, (2) полный, (3) рабочий размер трека горизонтального скроллбара
   int               ThumbSizeHorz(void);
   int               TrackLengthHorz(void)               const { return(this.m_scrollbar_h!=NULL ? this.m_scrollbar_h.TrackLength() : 0);       }
   int               TrackEffectiveLengthHorz(void)            { return(this.TrackLengthHorz()-this.ThumbSizeHorz());                           }
//--- Рассчитывает и возвращает размер (1) ползунка, (2) полный, (3) рабочий размер трека вертикального скроллбара
   int               ThumbSizeVert(void);
   int               TrackLengthVert(void)               const { return(this.m_scrollbar_v!=NULL ? this.m_scrollbar_v.TrackLength() : 0);       }
   int               TrackEffectiveLengthVert(void)            { return(this.TrackLengthVert()-this.ThumbSizeVert());                           }
//--- Размер видимой области содержимого по (1) горизонтали, (2) вертикали
   int               ContentVisibleHorz(void)            const { return int(this.Width()-this.BorderWidthLeft()-this.BorderWidthRight());       }
   int               ContentVisibleVert(void)            const { return int(this.Height()-this.BorderWidthTop()-this.BorderWidthBottom());      }
   
//--- Полный размер содержимого по (1) горизонтали, (2) вертикали
   int               ContentSizeHorz(void);
   int               ContentSizeVert(void);
   
//--- Позиция содержимого по (1) горизонтали, (2) вертикали
   int               ContentPositionHorz(void);
   int               ContentPositionVert(void);
//--- Рассчитывает и возвращает величину смещения содержимого по (1) горизонтали, (2) вертикали в зависимости от положения ползунка
   int               CalculateContentOffsetHorz(const uint thumb_position);
   int               CalculateContentOffsetVert(const uint thumb_position);
//--- Рассчитывает и возвращает величину смещения ползунка по (1) горизонтали, (2) вертикали в зависимости от положения контента
   int               CalculateThumbOffsetHorz(const uint content_position);
   int               CalculateThumbOffsetVert(const uint content_position);
   
//--- Смещает содержимое по (1) горизонтали, (2) вертикали на указанное значение
   bool              ContentShiftHorz(const int value);
   bool              ContentShiftVert(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              ScrollBarHorzIsVisible(void)        const { return this.m_visible_scrollbar_h;                                             }
   bool              ScrollBarVertIsVisible(void)        const { return this.m_visible_scrollbar_v;                                             }

//--- Возвращает прикреплённый элемент (содержимое контейнера)
   CElementBase     *GetAttachedElement(void)                  { return this.GetAttachedElementAt(2);                                           }

//--- Создаёт и добавляет (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);
   
//--- (1) Отображает объект на всех периодах графика, (2) помещает объект на передний план
   virtual void      Show(const bool chart_redraw);
   virtual void      BringToTop(const bool chart_redraw);
   
//--- Рисует внешний вид
   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 long chart_id, const int wnd, const int x, const int y, const int w, const int h);
                    ~CContainer (void) {}
  };

In the initialization method, keep original dimensions of the border:

//+------------------------------------------------------------------+
//| CContainer::Инициализация                                        |
//+------------------------------------------------------------------+
void CContainer::Init(void)
  {
//--- Инициализация родительского объекта
   CPanel::Init();
//--- Ширина рамки
   this.SetBorderWidth(0);
//--- Запоминаем установленную ширину рамки с каждой стороны
   this.m_init_border_size_top   = (int)this.BorderWidthTop();
   this.m_init_border_size_bottom= (int)this.BorderWidthBottom();
   this.m_init_border_size_left  = (int)this.BorderWidthLeft();
   this.m_init_border_size_right = (int)this.BorderWidthRight();
   
//--- Создаём горизонтальный скроллбар
   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;
  }

A Method That Displays the Container:

//+------------------------------------------------------------------+
//| CContainer::Отображает объект на всех периодах графика           |
//+------------------------------------------------------------------+
void CContainer::Show(const bool chart_redraw)
  {
//--- Если объект уже видимый, или не должен отображаться в контейнере - уходим
   if(!this.m_hidden || !this.m_visible_in_container)
      return;
      
//--- Отображаем панель
   CCanvasBase::Show(false);
//--- Отображаем прикреплённые объекты
   for(int i=0;i<this.m_list_elm.Total();i++)
     {
      CElementBase *elm=this.GetAttachedElementAt(i);
      if(elm!=NULL)
        {
         if(elm.Type()==ELEMENT_TYPE_SCROLLBAR_H && !this.m_visible_scrollbar_h)
            continue;
         if(elm.Type()==ELEMENT_TYPE_SCROLLBAR_V && !this.m_visible_scrollbar_v)
            continue;
         elm.Show(false);
        }
     }
//--- Если указано - перерисовываем график
   if(chart_redraw)
      ::ChartRedraw(this.m_chart_id);
  }

First, the base panel is displayed, and then the container contents are displayed in a loop through the list of attached objects, except for scrollbars, if the display flag is not set for them.

A Method That Puts the Container in the Foreground:

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

Everything is similar to the previous method.

Refine The Method That Checks Element Size to Display Scrollbars:

//+------------------------------------------------------------------+
//| CContainer::Проверяет размеры элемента                           |
//| для отображения полос прокрутки                                  |
//+------------------------------------------------------------------+
void CContainer::CheckElementSizes(CElementBase *element)
  {
//--- Если передан пустой элемент, или прокрутка запрещена, или скроллбары не созданы - уходим
   if(element==NULL || !this.m_scroll_flag || this.m_scrollbar_h==NULL || this.m_scrollbar_v==NULL)
      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.ContentVisibleHorz())
     {
      this.m_visible_scrollbar_h=true;
      this.m_scrollbar_h.SetVisibleInContainer(true);
     }
//--- Если высота элемента больше высоты видимой области контейнера -
//--- устанавливаем флаг отображения вертикальной полосы прокрутки
//--- и флаг отображения в контейнере
   if(element.Height()>this.ContentVisibleVert())
     {
      this.m_visible_scrollbar_v=true;
      this.m_scrollbar_v.SetVisibleInContainer(true);
     }

//--- Если обе полосы прокрутки должны быть отображены
   if(this.m_visible_scrollbar_h && this.m_visible_scrollbar_v)
     {
      //--- Корректируем размер обеих полос прокрутки на толщину скроллбара и 
      //--- устанавливаем размеры ползунков под новые размеры треков
      if(this.m_scrollbar_v.ResizeH(this.Height()-DEF_SCROLLBAR_TH))
         this.m_scrollbar_v.SetThumbSize(this.ThumbSizeVert());
      if(this.m_scrollbar_h.ResizeW(this.Width() -DEF_SCROLLBAR_TH))
         this.m_scrollbar_h.SetThumbSize(this.ThumbSizeHorz());
     }
     
//--- Если горизонтальная полоса прокрутки должна быть показана
   if(this.m_visible_scrollbar_h)
     {
      //--- Уменьшаем размер видимого окна контейнера снизу на толщину полосы прокрутки + 1 пиксель
      this.SetBorderWidthBottom(this.m_scrollbar_h.Height()+1);
      //--- Корректируем размер ползунка под новый размер полосы прокрутки и
      //--- переносим скроллбар на передний план, делая его при этом видимым
      this.m_scrollbar_h.SetThumbSize(this.ThumbSizeHorz());
      
      int end_track=this.X()+this.m_scrollbar_h.TrackBegin()+this.m_scrollbar_h.TrackLength();
      int thumb_right=this.m_scrollbar_h.GetThumb().Right();
      if(thumb_right>=end_track)
        {
         int pos=end_track-this.ThumbSizeHorz();
         this.m_scrollbar_h.SetThumbPosition(pos);
        }     
      this.m_scrollbar_h.SetVisibleInContainer(true);
      this.m_scrollbar_h.MoveY(this.Bottom()-DEF_SCROLLBAR_TH);
      this.m_scrollbar_h.BringToTop(false);
     }
   else
     {
      //--- Восстанавливаем размер видимого окна контейнера снизу,
      //--- скрываем горизонтальный скроллбар, ставим запрет его отображения в контейнере,
      //--- и устанавливаем высоту вертикального скроллбара по высоте контейнера
      this.SetBorderWidthBottom(this.m_init_border_size_bottom);
      this.m_scrollbar_h.Hide(false);
      this.m_scrollbar_h.SetVisibleInContainer(false);
      if(this.m_scrollbar_v.ResizeH(this.Height()-1))
         this.m_scrollbar_v.SetThumbSize(this.ThumbSizeVert());
     }
     
//--- Если вертикальная полоса прокрутки должна быть показана
   if(this.m_visible_scrollbar_v)
     {
      //--- Уменьшаем размер видимого окна контейнера справа на толщину полосы прокрутки + 1 пиксель
      this.SetBorderWidthRight(this.m_scrollbar_v.Width()+1);
      //--- Корректируем размер ползунка под новый размер полосы прокрутки и
      //--- переносим скроллбар на передний план, делая его при этом видимым
      this.m_scrollbar_v.SetThumbSize(this.ThumbSizeVert());
      
      int end_track=this.Y()+this.m_scrollbar_v.TrackBegin()+this.m_scrollbar_v.TrackLength();
      int thumb_bottom=this.m_scrollbar_v.GetThumb().Bottom();
      if(thumb_bottom>=end_track)
        {
         int pos=end_track-this.ThumbSizeVert();
         this.m_scrollbar_v.SetThumbPosition(pos);
        }
      this.m_scrollbar_v.SetVisibleInContainer(true);
      this.m_scrollbar_v.MoveX(this.Right()-DEF_SCROLLBAR_TH);
      this.m_scrollbar_v.BringToTop(false);
     }
   else
     {
      //--- Восстанавливаем размер видимого окна контейнера справа,
      //--- скрываем вертикальный скроллбар, ставим запрет его отображения в контейнере,
      //--- и устанавливаем ширину горизонтального скроллбара по ширине контейнера
      this.SetBorderWidthRight(this.m_init_border_size_right);
      this.m_scrollbar_v.Hide(false);
      this.m_scrollbar_v.SetVisibleInContainer(false);
      if(this.m_scrollbar_h.ResizeW(this.Width()-1))
         this.m_scrollbar_h.SetThumbSize(this.ThumbSizeHorz());
     }
//--- Если любая из полос прокрутки видима - обрезаем привязанный элемент по новым размерам видимой области 
   if(this.m_visible_scrollbar_h || this.m_visible_scrollbar_v)
     {
      element.ObjectTrim();
     }
  }

The logic of the method is described in detail in the comments in the code and no questions should occur, I believe. In any case, you can always ask questions in the discussion to the article.

A Handler For Dragging the Edges and Corners of an Element:

//+------------------------------------------------------------------+
//| CContainer::Обработчик перетаскивания граней и углов элемента    |
//+------------------------------------------------------------------+
void CContainer::ResizeActionDragHandler(const int x, const int y)
  {
//--- Проверяем валидность полос прокрутки
   if(this.m_scrollbar_h==NULL || this.m_scrollbar_v==NULL)
      return;
   
//--- В зависимости от региона взаимодействия с курсором
   switch(this.ResizeRegion())
     {
      //--- Изменение размера за правую границу
      case CURSOR_REGION_RIGHT :
         //--- Если новая ширина успешно установлена
         if(this.ResizeZoneRightHandler(x,y))
           {
            //--- проверяем размер содержимого контейнера для отображения скроллбаров,
            //--- смещаем содержимое по новой позиции ползунка горизонтального скроллбара
            this.CheckElementSizes(this.GetAttachedElement());
            this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition());
           }
        break;
        
      //--- Изменение размера за нижнюю границу
      case CURSOR_REGION_BOTTOM :
         //--- Если новая высота успешно установлена
         if(this.ResizeZoneBottomHandler(x,y))
           {
            //--- проверяем размер содержимого контейнера для отображения скроллбаров,
            //--- смещаем содержимое по новой позиции ползунка вертикального скроллбара
            this.CheckElementSizes(this.GetAttachedElement());
            this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition());
           }
        break;
        
      //--- Изменение размера за левую границу
      case CURSOR_REGION_LEFT :
         //--- Если новые координата X и ширина успешно установлены
         if(this.ResizeZoneLeftHandler(x,y))
           {
            //--- проверяем размер содержимого контейнера для отображения скроллбаров,
            //--- смещаем содержимое по новой позиции ползунка горизонтального скроллбара
            this.CheckElementSizes(this.GetAttachedElement());
            this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition());
           }
        break;
        
      //--- Изменение размера за верхнюю границу
      case CURSOR_REGION_TOP :
         //--- Если новые координата Y и высота успешно установлены
         if(this.ResizeZoneTopHandler(x,y))
           {
            //--- проверяем размер содержимого контейнера для отображения скроллбаров,
            //--- смещаем содержимое по новой позиции ползунка вертикального скроллбара
            this.CheckElementSizes(this.GetAttachedElement());
            this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition());
           }
        break;
        
      //--- Изменение размера за правый нижний угол
      case CURSOR_REGION_RIGHT_BOTTOM :
         //--- Если новые ширина и высота успешно установлены
         if(this.ResizeZoneRightBottomHandler(x,y))
           {
            //--- проверяем размер содержимого контейнера для отображения скроллбаров,
            //--- смещаем содержимое по новым позициям ползунков скроллбаров
            this.CheckElementSizes(this.GetAttachedElement());
            this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition());
            this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition());
           }
        break;
        
      //--- Изменение размера за правый верхний угол
      case CURSOR_REGION_RIGHT_TOP :
         //--- Если новые координата Y, ширина и высота успешно установлены
         if(this.ResizeZoneRightTopHandler(x,y))
           {
            //--- проверяем размер содержимого контейнера для отображения скроллбаров,
            //--- смещаем содержимое по новым позициям ползунков скроллбаров
            this.CheckElementSizes(this.GetAttachedElement());
            this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition());
            this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition());
           }
        break;
      
      //--- Изменение размера за левый нижний угол
      case CURSOR_REGION_LEFT_BOTTOM :
         //--- Если новые координата X, ширина и высота успешно установлены
         if(this.ResizeZoneLeftBottomHandler(x,y))
           {
            //--- проверяем размер содержимого контейнера для отображения скроллбаров,
            //--- смещаем содержимое по новым позициям ползунков скроллбаров
            this.CheckElementSizes(this.GetAttachedElement());
            this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition());
            this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition());
           }
        break;
      
      //--- Изменение размера за левый верхний угол
      case CURSOR_REGION_LEFT_TOP :
         //--- Если новые координаты X и Y, ширина и высота успешно установлены
         if(this.ResizeZoneLeftTopHandler(x,y)) {}
           {
            //--- проверяем размер содержимого контейнера для отображения скроллбаров,
            //--- смещаем содержимое по новым позициям ползунков скроллбаров
            this.CheckElementSizes(this.GetAttachedElement());
            this.ContentShiftHorz(this.m_scrollbar_h.ThumbPosition());
            this.ContentShiftVert(this.m_scrollbar_v.ThumbPosition());
           }
        break;
      
      //--- По умолчанию - уходим
      default: return;
     }
   ::ChartRedraw(this.m_chart_id);
  }

Here, depending on which edge or angle the dimensions (and coordinates) of the element are resized, the corresponding resizing handlers are called by dragging the edge or corner. After successful operation of the handler, the new position of the container contents is adjusted according to the position of scrollbar thumbs.

These are all the improvements that are necessary to resize elements using the mouse cursor. We have not considered some minor corrections and changes to the code here, as they only bring improvements in the perception of the code, methods, and some visual components when elements interact with the mouse cursor. You can view all the changes in the codes attached to the article.



Testing the Result

For testing, in the terminal directory \MQL5\Indicators\ in the Tables\ subfolder, create a new indicator named iTestResize.mq5:

//+------------------------------------------------------------------+
//|                                                  iTestResize.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()
  {
//--- Ищем подокно графика
   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);           // Ширина рамки (отступ видимой области на один пиксель с каждой стороны контейнера)
   container.SetResizable(true);          // Возможность менять размеры перетаскиванием за грани и углы
   container.SetName("Main container");   // Наименование
   
//--- Присоединяем к контейнеру элемент 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);
  }
//+------------------------------------------------------------------+
//| Custom deindicator initialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- Удаляем элемент Контейнер и уничтожаем менеджер общих ресурсов библиотеки
   delete container;
   CCommonManager::DestroyInstance();
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//---
   
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| ChartEvent function                                              |
//+------------------------------------------------------------------+
void OnChartEvent(const int id,
                  const long &lparam,
                  const double &dparam,
                  const string &sparam)
  {
//--- Вызываем обработчик OnChartEvent элемента Контейнер
   container.OnChartEvent(id,lparam,dparam,sparam);
  }
//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void OnTimer(void)
  {
//--- Вызываем обработчик OnTimer элемента Контейнер
   container.OnTimer();
  }

The indicator practically does not differ from the test indicator from the previous article.

Compile the indicator and run it on the chart:

Obviously, the stated functionality operates correctly. It is difficult to capture edges of an element where they contact scroll bars. However, resizable elements usually do not consist of a single control. As an example, a graphical element "Form". It has sufficient indentations from all the controls, thanks to which you can effortlessly find the capture point for dragging the element boundary with the mouse.

There are still some shortcomings that we will gradually get rid of as we continue to work on creating the TableView graphical element.


Conclusion

Today we are one step closer to completing work on the TableView control, which will allow us to create and display tabular data in our programs. Implementation of the View component is quite voluminous and complex, but the result should close most of the requirements for tabular representation of data and working with them.

In the next article, we will start creating interactive tabular data headers that allow you to manage columns of the table and its rows.

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  iTestResize.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

All created files are attached to the article for self-study. The archive file can be unzipped to the terminal folder, and all files will be located in the desired folder: \MQL5\Indicators\Tables\.


Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/18941

Attached files |
Base.mqh (282.64 KB)
Controls.mqh (516.62 KB)
iTestResize.mq5 (9.67 KB)
MQL5.zip (73.34 KB)
Codex Pipelines, from Python to MQL5, for Indicator Selection: A Multi-Quarter Analysis of the XLF ETF with Machine Learning Codex Pipelines, from Python to MQL5, for Indicator Selection: A Multi-Quarter Analysis of the XLF ETF with Machine Learning
We continue our look at how the selection of indicators can be pipelined when facing a ‘none-typical’ MetaTrader asset. MetaTrader 5 is primarily used to trade forex, and that is good given the liquidity on offer, however the case for trading outside of this ‘comfort-zone’, is growing bolder with not just the overnight rise of platforms like Robinhood, but also the relentless pursuit of an edge for most traders. We consider the XLF ETF for this article and also cap our revamped pipeline with a simple MLP.
From Novice to Expert: Trading the RSI with Market Structure Awareness From Novice to Expert: Trading the RSI with Market Structure Awareness
In this article, we will explore practical techniques for trading the Relative Strength Index (RSI) oscillator with market structure. Our focus will be on channel price action patterns, how they are typically traded, and how MQL5 can be leveraged to enhance this process. By the end, you will have a rule-based, automated channel-trading system designed to capture trend continuation opportunities with greater precision and consistency.
Building AI-Powered Trading Systems in MQL5 (Part 7): Further Modularization and Automated Trading Building AI-Powered Trading Systems in MQL5 (Part 7): Further Modularization and Automated Trading
In this article, we enhance the AI-powered trading system's modularity by separating UI components into a dedicated include file. The system now automates trade execution based on AI-generated signals, parsing JSON responses for BUY/SELL/NONE with entry/SL/TP, visualizing patterns like engulfing or divergences on charts with arrows, lines, and labels, and optional auto-signal checks on new bars.
Introduction to MQL5 (Part 31): Mastering API and WebRequest Function in MQL5 (V) Introduction to MQL5 (Part 31): Mastering API and WebRequest Function in MQL5 (V)
Learn how to use WebRequest and external API calls to retrieve recent candle data, convert each value into a usable type, and save the information neatly in a table format. This step lays the groundwork for building an indicator that visualizes the data in candle format.