DoEasy. Элементы управления (Часть 21): Элемент управления SplitContainer. Разделитель панелей

Artyom Trishkin | 7 октября, 2022

Содержание


Концепция

В прошлой статье мы начали работу по созданию элемента управления SplitContainer. На данный момент библиотека может создать такой элемент в виде статического объекта с двумя панелями со значениями параметров по умолчанию. Объект имеет две панели, разделённые разделителем. В оригинальном объекте в MS Visual Studio мы можем перемещать разделитель, тем самым в итоге изменяя размеры панелей.

При наведении курсора мышки на область разделителя появляется характерный курсор, указывающий на возможность смещения разделителя ( ), а при его захвате мышкой, разделитель закрашивается штрихованной областью, которую можно перемещать, тем самым указывая новое расположение разделителя. При отпускании кнопки мышки панели изменяют свои размеры в соответствии с новым положением разделителя.

Так как в MQL5 не предусмотрена возможность смены внешнего вида курсора, то пока не будем делать никаких "сигнализаторов" о том, что "тут возможно схватить и перетащить", а просто сразу будем накладывать штрихованную область на область разделителя, тем самым и указывая на возможность его перемещения. В объекте SplitContainer MS Visual Studio порядок такой:

  1. При наведении курсора на область разделителя появляется курсор, указывающий на возможность перемещения;
  2. При нажатии и удержании клавиши мышки (но не смещении курсора) появляется пунктирный прямоугольник, очерчивающий область разделителя;
  3. При смещении курсора мышки появляется заштрихованная область, размером с разделитель и движущаяся вслед за курсором, указывая на новое положение разделителя, которое будет при отпускании кнопки мышки;
  4. При отпускании кнопки мышки размеры панелей перестраиваются под новое положение разделителя.

У нас же будет более простая схема:

  1. При наведении курсора на область разделителя появляется штрихованная область;
  2. При захвате мышкой штрихованной области и её перемещении, сразу меняются размеры панелей в соответствии с новым положением разделителя;
  3. При отпускании кнопки мышки и уводе курсора с области разделителя, штрихованная область скрывается, а панели остаются с новыми размерами.

Объект-разделитель будем строить как производный объект от базового объекта всех WinForms-объектов библиотеки — от класса CWinFormBase, в котором будут переопределены виртуальные методы для очистки и перерисовки объекта — в них будет рисоваться штрихованное поле, заполняющее всю область объекта. Управлять видимостью этого объекта будем из обработчика событий элемента управления SplitContainer. При наведении мышки на область управления объект будет отображаться, а при уводе курсора с этой области — скрываться. В такой области могут находиться как сам разделитель в данном объекте, так и управляющие кнопки в других объектах, такие, как сворачивание/разворачивание/закрытие окна в будущих объектах библиотеки, и т.п.


Доработка классов библиотеки

Для указания координат и размеров области управления добавим новые целочисленные свойства объекта-графического элемента и новые идентификаторы событий и состояний мышки.

В файле \MQL5\Include\DoEasy\Defines.mqh добавим в список возможных состояний мышки относительно формы новые идентификаторы:

//+------------------------------------------------------------------+
//| Список возможных состояний мышки относительно формы              |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_FORM_STATE
  {
   MOUSE_FORM_STATE_NONE = 0,                         // Неопределённое состояние
//--- За пределами формы
   MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED,         // Курсор за пределами формы, кнопки мышки не нажаты
   MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED,             // Курсор за пределами формы, нажата кнопка мышки (любая)
   MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL,               // Курсор за пределами формы, прокручивается колёсико мышки
//--- В пределах формы
   MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED,          // Курсор в пределах формы, кнопки мышки не нажаты
   MOUSE_FORM_STATE_INSIDE_FORM_PRESSED,              // Курсор в пределах формы, нажата кнопка мышки (любая)
   MOUSE_FORM_STATE_INSIDE_FORM_WHEEL,                // Курсор в пределах формы, прокручивается колёсико мышки
//--- В пределах области заголовка окна
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED,   // Курсор в пределах активной области, кнопки мышки не нажаты
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED,       // Курсор в пределах активной области, нажата кнопка мышки (любая)
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL,         // Курсор в пределах активной области, прокручивается колёсико мышки
   MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED,      // Курсор в пределах активной области, отжата кнопка мышки (левая)
//--- В пределах области прокрутки окна
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED,   // Курсор в пределах области прокрутки окна, кнопки мышки не нажаты
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED,       // Курсор в пределах области прокрутки окна, нажата кнопка мышки (любая)
   MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL,         // Курсор в пределах области прокрутки окна, прокручивается колёсико мышки
//--- В пределах области изменения размеров окна
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED,   // Курсор в пределах области изменения размеров окна, кнопки мышки не нажаты
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED,       // Курсор в пределах области изменения размеров окна, нажата кнопка мышки (любая)
   MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL,         // Курсор в пределах области изменения размеров окна, прокручивается колёсико мышки
//--- В пределах области резделителя окна
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_NOT_PRESSED, // Курсор в пределах области резделителя окна, кнопки мышки не нажаты
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_PRESSED,     // Курсор в пределах области резделителя окна, нажата кнопка мышки (любая)
   MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_WHEEL,       // Курсор в пределах области резделителя окна, прокручивается колёсико мышки
  };
//+------------------------------------------------------------------+

Область изменения размеров окна — это та область сверху, снизу, слева и справа, при наведении курсора на которую появляется возможность изменения размеров графического элемента, для которого такая возможность конструктивно предусмотрена. Сюда мы вписали эти идентификаторы с прицелом на будущее — нам всё равно необходимо будет делать изменение размеров окон мышкой, и почему бы не вписать эти идентификаторы уже сейчас.


В список возможных событий мышки впишем новые идентификаторы событий, соответствующие новым состояниям мышки относительно формы:

//+------------------------------------------------------------------+
//| Список возможных событий мышки                                   |
//+------------------------------------------------------------------+
enum ENUM_MOUSE_EVENT
  {
   MOUSE_EVENT_NO_EVENT = CHART_OBJ_EVENTS_NEXT_CODE, // Нет события
//---
   MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED,              // Курсор за пределами формы, кнопки мышки не нажаты
   MOUSE_EVENT_OUTSIDE_FORM_PRESSED,                  // Курсор за пределами формы, нажата кнопка мышки (любая)
   MOUSE_EVENT_OUTSIDE_FORM_WHEEL,                    // Курсор за пределами формы, прокручивается колёсико мышки
//--- В пределах формы
   MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED,               // Курсор в пределах формы, кнопки мышки не нажаты
   MOUSE_EVENT_INSIDE_FORM_PRESSED,                   // Курсор в пределах формы, нажата кнопка мышки (любая)
   MOUSE_EVENT_INSIDE_FORM_WHEEL,                     // Курсор в пределах формы, прокручивается колёсико мышки
//--- В пределах области заголовка окна
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED,        // Курсор в пределах активной области, кнопки мышки не нажаты
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED,            // Курсор в пределах активной области, нажата кнопка мышки (любая)
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL,              // Курсор в пределах активной области, прокручивается колёсико мышки
   MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED,           // Курсор в пределах активной области, отжата кнопка мышки (левая)
//--- В пределах области прокрутки окна
   MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED,        // Курсор в пределах области прокрутки окна, кнопки мышки не нажаты
   MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED,            // Курсор в пределах области прокрутки окна, нажата кнопка мышки (любая)
   MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL,              // Курсор в пределах области прокрутки окна, прокручивается колёсико мышки
//--- В пределах области изменения размеров окна
   MOUSE_EVENT_INSIDE_RESIZE_AREA_NOT_PRESSED,        // Курсор в пределах области изменения размеров окна, кнопки мышки не нажаты
   MOUSE_EVENT_INSIDE_RESIZE_AREA_PRESSED,            // Курсор в пределах области изменения размеров окна, нажата кнопка мышки (любая)
   MOUSE_EVENT_INSIDE_RESIZE_AREA_WHEEL,              // Курсор в пределах области изменения размеров окна, прокручивается колёсико мышки
//--- В пределах области резделителя окна
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_NOT_PRESSED,      // Курсор в пределах области резделителя окна, кнопки мышки не нажаты
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_PRESSED,          // Курсор в пределах области резделителя окна, нажата кнопка мышки (любая)
   MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL,            // Курсор в пределах области резделителя окна, прокручивается колёсико мышки
  };
#define MOUSE_EVENT_NEXT_CODE  (MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL+1)  // Код следующего события после последнего кода события мышки
//+------------------------------------------------------------------+

Так как здесь появились новые константы перечисления, то в макроподстановку MOUSE_EVENT_NEXT_CODE необходимо вписать самую последнюю константу перечисления MOUSE_EVENT_INSIDE_SPLITTER_AREA_WHEEL вместо прежней MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL.


В список типов графических элементов добавим новый тип вспомогательного объекта, который сегодня будем делать:

//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Стандартный графический объект
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Расширенный стандартный графический объект
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Объект тени
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Элемент
   GRAPH_ELEMENT_TYPE_FORM,                           // Форма
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Окно
   //--- WinForms
   GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                    // Подложка объекта-панели
   GRAPH_ELEMENT_TYPE_WF_BASE,                        // Windows Forms Base
   //--- Ниже нужно вписывать типы объектов "контейнер"
   GRAPH_ELEMENT_TYPE_WF_CONTAINER,                   // Windows Forms базовый объект-контейнер
   GRAPH_ELEMENT_TYPE_WF_PANEL,                       // Windows Forms Panel
   GRAPH_ELEMENT_TYPE_WF_GROUPBOX,                    // Windows Forms GroupBox
   GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,                 // Windows Forms TabControl
   GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,             // Windows Forms SplitContainer
   //--- Ниже нужно вписывать типы объектов "стандартный элемент управления"
   GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,                 // Windows Forms базовый стандартный элемент управления
   GRAPH_ELEMENT_TYPE_WF_LABEL,                       // Windows Forms Label
   GRAPH_ELEMENT_TYPE_WF_BUTTON,                      // Windows Forms Button
   GRAPH_ELEMENT_TYPE_WF_CHECKBOX,                    // Windows Forms CheckBox
   GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,                 // Windows Forms RadioButton
   GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX,           // Базовый объект-список Windows Forms элементов
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX,                    // Windows Forms ListBox
   GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,            // Windows Forms CheckedListBox
   GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,             // Windows Forms ButtonListBox
   //--- Вспомогательные элементы WinForms-объектов
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,               // Windows Forms ListBoxItem
   GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,                  // Windows Forms TabHeader
   GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,                   // Windows Forms TabField
   GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,       // Windows Forms SplitContainerPanel
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,                // Windows Forms ArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,             // Windows Forms UpArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,           // Windows Forms DownArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,           // Windows Forms LeftArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,          // Windows Forms RightArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,        // Windows Forms UpDownArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,        // Windows Forms LeftRightArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_SPLITTER,                    // Windows Forms Splitter
  };
//+------------------------------------------------------------------+


В список возможных событий элементов управления WinForms добавим новое событие — перемещение разделителя:

//+------------------------------------------------------------------+
//| Список возможных событий элементов управления WinForms           |
//+------------------------------------------------------------------+
enum ENUM_WF_CONTROL_EVENT
  {
   WF_CONTROL_EVENT_NO_EVENT = GRAPH_OBJ_EVENTS_NEXT_CODE,// Нет события
   WF_CONTROL_EVENT_CLICK,                            // Событие "Щелчок по элементу управления"
   WF_CONTROL_EVENT_CLICK_CANCEL,                     // Событие "Отмена щелчка по элементу управления"
   WF_CONTROL_EVENT_TAB_SELECT,                       // Событие "Выбор вкладки элемента управления TabControl"
   WF_CONTROL_EVENT_CLICK_SCROLL_LEFT,                // Событие "Щелчок по кнопке влево элемента управления"
   WF_CONTROL_EVENT_CLICK_SCROLL_RIGHT,               // Событие "Щелчок по кнопке влево элемента управления"
   WF_CONTROL_EVENT_CLICK_SCROLL_UP,                  // Событие "Щелчок по кнопке влево элемента управления"
   WF_CONTROL_EVENT_CLICK_SCROLL_DOWN,                // Событие "Щелчок по кнопке влево элемента управления"
   WF_CONTROL_EVENT_SPLITTER_MOVE,                    // Событие "Перемещение разделителя элемента управления"
  };
#define WF_CONTROL_EVENTS_NEXT_CODE (WF_CONTROL_EVENT_SPLITTER_MOVE+1)  // Код следующего события после последнего кода события графических элементов
//+------------------------------------------------------------------+

Здесь точно так же, как и выше — в макроподстановку WF_CONTROL_EVENTS_NEXT_CODE необходимо вписать самую последнюю константу перечисления WF_CONTROL_EVENT_SPLITTER_MOVE, так как это теперь самая последняя константа этого перечисления.


Для возможности указания каким образом должен в объекте располагаться разделитель, напишем перечисление:

//+------------------------------------------------------------------+
//| Расположение разделителя в Split Container                       |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION
  {
   CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL,        // Вертикальный
   CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL,      // Горизонтальный
  };
//+------------------------------------------------------------------+
//| Целочисленные свойства графического элемента на канвасе          |
//+------------------------------------------------------------------+


В список целочисленных свойств графического элемента на канвасе добавим новые свойства и увеличим их общее количество до 122:

//+------------------------------------------------------------------+
//| Целочисленные свойства графического элемента на канвасе          |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_INTEGER
  {
   CANV_ELEMENT_PROP_ID = 0,                          // Идентификатор элемента
   CANV_ELEMENT_PROP_TYPE,                            // Тип графического элемента

   //---...
   //---...

   CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,              // Ширина области видимости
   CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,             // Высота области видимости
   CANV_ELEMENT_PROP_CONTROL_AREA_X,                  // X-координата области управления
   CANV_ELEMENT_PROP_CONTROL_AREA_Y,                  // Y-координата области управления
   CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,              // Ширина области управления
   CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,             // Высота области управления
   CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,             // X-координата области прокрутки справа
   CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,             // Y-координата области прокрутки справа
   CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,         // Ширина области прокрутки справа
   CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,        // Высота области прокрутки справа
   CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,            // X-координата области прокрутки снизу
   CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,            // Y-координата области прокрутки снизу
   CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,        // Ширина области прокрутки снизу
   CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,       // Высота области прокрутки снизу
   CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,          // Ширина области левой грани
   CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,        // Ширина области нижней грани
   CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,         // Ширина области правой грани
   CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,           // Ширина области верхней грани
   CANV_ELEMENT_PROP_DISPLAYED,                       // Флаг отображения не скрытого элемента управления
   CANV_ELEMENT_PROP_GROUP,                           // Группа, к которой принадлежит графический элемент

   //---...
   //---...

   CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,// Расстояние от края до разделителя
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,  // Толщина разделителя
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,// Расположение разделителя
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,// Флаг свёрнутости панели 1
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE, // Минимальный размер панели 1
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,// Флаг свёрнутости панели 1
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE, // Минимальный размер панели 2
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (122)         // Общее количество целочисленных свойств
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Количество неиспользуемых в сортировке целочисленных свойств
//+------------------------------------------------------------------+


В список возможных критериев сортировки графических элементов на канвасе добавим новые критерии сортировки по новым свойствам:

//+------------------------------------------------------------------+
//| Возможные критерии сортировки графических элементов на канвасе   |
//+------------------------------------------------------------------+
#define FIRST_CANV_ELEMENT_DBL_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP)
#define FIRST_CANV_ELEMENT_STR_PROP  (CANV_ELEMENT_PROP_INTEGER_TOTAL-CANV_ELEMENT_PROP_INTEGER_SKIP+CANV_ELEMENT_PROP_DOUBLE_TOTAL-CANV_ELEMENT_PROP_DOUBLE_SKIP)
enum ENUM_SORT_CANV_ELEMENT_MODE
  {
//--- Сортировка по целочисленным свойствам
   SORT_BY_CANV_ELEMENT_ID = 0,                       // Сортировать по идентификатору элемента
   SORT_BY_CANV_ELEMENT_TYPE,                         // Сортировать по типу графического элемента

   //---...
   //---...

   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_WIDTH,           // Сортировать по ширине области видимости
   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_HEIGHT,          // Сортировать по высоте области видимости
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_X,               // Сортировать по X-координате области управления
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_Y,               // Сортировать по Y-координате области управления
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_WIDTH,           // Сортировать по ширине области управления
   SORT_BY_CANV_ELEMENT_CONTROL_AREA_HEIGHT,          // Сортировать по высоте области управления
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_X_RIGHT,          // Сортировать по X-координате области прокрутки справа
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_Y_RIGHT,          // Сортировать по Y-координате области прокрутки справа
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_WIDTH_RIGHT,      // Сортировать по ширине области прокрутки справа
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_HEIGHT_RIGHT,     // Сортировать по высоте области прокрутки справа
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_X_BOTTOM,         // Сортировать по X-координате области прокрутки снизу
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_Y_BOTTOM,         // Сортировать по Y-координате области прокрутки снизу
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_WIDTH_BOTTOM,     // Сортировать по ширине области прокрутки снизу
   SORT_BY_CANV_ELEMENT_SCROLL_AREA_HEIGHT_BOTTOM,    // Сортировать по высоте области прокрутки снизу
   SORT_BY_CANV_ELEMENT_BORDER_LEFT_AREA_WIDTH,       // Сортировать по ширине области левой грани
   SORT_BY_CANV_ELEMENT_BORDER_BOTTOM_AREA_WIDTH,     // Сортировать по ширине области нижней грани
   SORT_BY_CANV_ELEMENT_BORDER_RIGHT_AREA_WIDTH,      // Сортировать по ширине области правой грани
   SORT_BY_CANV_ELEMENT_BORDER_TOP_AREA_WIDTH,        // Сортировать по ширине области верхней грани
   SORT_BY_CANV_ELEMENT_DISPLAYED,                    // Сортировать по флагу отображения не скрытого элемента управления
   SORT_BY_CANV_ELEMENT_GROUP,                        // Сортировать по группе, к которой принадлежит графический элемент

   //---...
   //---...

   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_SPLITTER_DISTANCE,// Сортировать по расстоянию от края до разделителя
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_SPLITTER_WIDTH,  // Сортировать по толщине разделителя
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_SPLITTER_ORIENTATION,// Сортировать по расположению разделителя
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL1_COLLAPSED,// Сортировать по флагу свёрнутости панели 1
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL1_MIN_SIZE, // Сортировать по минимальному размеру панели 1
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL2_COLLAPSED,// Сортировать по флагу свёрнутости панели 1
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL2_MIN_SIZE, // Сортировать по минимальному размеру панели 2
//--- Сортировка по вещественным свойствам

//--- Сортировка по строковым свойствам
   SORT_BY_CANV_ELEMENT_NAME_OBJ = FIRST_CANV_ELEMENT_STR_PROP,// Сортировать по имени объекта-элемента
   SORT_BY_CANV_ELEMENT_NAME_RES,                     // Сортировать по имени графического ресурса
   SORT_BY_CANV_ELEMENT_TEXT,                         // Сортировать по тексту графического элемента
   SORT_BY_CANV_ELEMENT_DESCRIPTION,                  // Сортировать по описанию графического элемента
  };
//+------------------------------------------------------------------+

Теперь мы сможем выбирать, сортировать и фильтровать объекты по этим новым свойствам.


В файле \MQL5\Include\DoEasy\Data.mqh впишем индексы новых сообщений:

   MSG_LIB_TEXT_TOP,                                  // Сверху
   MSG_LIB_TEXT_BOTTOM,                               // Снизу
   MSG_LIB_TEXT_LEFT,                                 // Слева
   MSG_LIB_TEXT_RIGHT,                                // Справа
   MSG_LIB_TEXT_VERTICAL,                             // Вертикально
   MSG_LIB_TEXT_HORISONTAL,                           // Горизонтально

...

   MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,   // Панель элемента управления SplitContainer
   MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,         // Элемент управления SplitContainer
   MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER,                // Элемент управления Splitter
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,            // Элемент управления ArrowButton
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,         // Элемент управления UpArrowButton

...

   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,          // Ширина области видимости
   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,         // Высота области видимости
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_X,              // X-координата области управления
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_Y,              // Y-координата области управления
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,          // Ширина области управления
   MSG_CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,         // Высота области управления
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,         // X-координата области прокрутки справа
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,         // Y-координата области прокрутки справа
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,     // Ширина области прокрутки справа
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,    // Высота области прокрутки справа
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,        // X-координата области прокрутки снизу
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,        // Y-координата области прокрутки снизу
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,    // Ширина области прокрутки снизу
   MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,   // Высота области прокрутки снизу
   MSG_CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,      // Ширина области левой грани
   MSG_CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,    // Ширина области нижней грани
   MSG_CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,     // Ширина области правой грани
   MSG_CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,       // Ширина области верхней грани
   MSG_CANV_ELEMENT_PROP_DISPLAYED,                   // Флаг отображения не скрытого элемента управления
   MSG_CANV_ELEMENT_PROP_ENABLED,                     // Флаг доступности элемента

...

   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE, // Расстояние от края до разделителя
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,    // Толщина разделителя
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION, // Расположение разделителя
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,  // Флаг свёрнутости панели 1
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,   // Минимальный размер панели 1

и текстовые сообщения, соответствующие вновь добавленным индексам:

   {"Сверху","Top"},
   {"Снизу","Bottom"},
   {"Слева","Left"},
   {"Справа","Right"},
   {"Вертикально","Vertical"},
   {"Горизонтально","Horisontal"},

...

   {"Панель элемента управления \"SplitContainer\"","Panel of the Control element \"SplitContainer\""},
   {"Элемент управления \"SplitContainer\"","Control element \"SplitContainer\""},
   {"Элемент управления \"Splitter\"","Control element \"Splitter\""},
   {"Элемент управления \"ArrowButton\"","Control element \"ArrowButton\""},
   {"Элемент управления \"UpArrowButton\"","Control element \"UpArrowButton\""},

...

   {"Ширина области видимости","Width of object visibility area"},
   {"Высота области видимости","Height of object visibility area"},
   {"X-координата области управления","X-coordinate of the control area"},
   {"Y-координата области управления","Y-coordinate of the control area"},
   {"Ширина области управления","Control area width"},
   {"Высота области управления","Control area height"},
   {"X-координата области прокрутки справа","X-coordinate of the right scroll area"},
   {"Y-координата области прокрутки справа","Y-coordinate of the right scroll area"},
   {"Ширина области прокрутки справа","Width of the right scroll area"},
   {"Высота области прокрутки справа","Height of the right scroll area"},
   {"X-координата области прокрутки снизу","X-coordinate of the bottom scroll area"},
   {"Y-координата области прокрутки снизу","Y-coordinate of the bottom scroll area"},
   {"Ширина области прокрутки снизу","Width of the bottom scroll area"},
   {"Высота области прокрутки снизу","Height of the bottom scroll area"},
   {"Ширина области левой грани","Width of the left border area"},
   {"Ширина области нижней грани","Width of the bottom border area"},
   {"Ширина области правой грани","Width of the right border area"},
   {"Ширина области верхней грани","Width of the top border area"},
   {"Флаг отображения не скрытого элемента управления","Flag that sets the display of a non-hidden control"},
   {"Флаг доступности элемента","Element Availability Flag"},

...

   {"Расстояние от края до разделителя","Distance from edge to splitter"},
   {"Толщина разделителя","Splitter Width"},
   {"Расположение разделителя","Splitter orientation"},
   {"Флаг свёрнутости панели 1","Flag to indicate that panel 1 is collapsed"},
   {"Минимальный размер панели 1","Min size of Panel 1"},


Для возможности получения описания типа нового объекта, в файле \MQL5\Include\DoEasy\Objects\Graph\GBaseObj.mqh в метод, возвращающий описание типа графического элемента, добавим вывод описания нового типа объекта:

//+------------------------------------------------------------------+
//| Возвращает описание типа графического элемента                   |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return
     (
      type==GRAPH_ELEMENT_TYPE_STANDARD                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)                 :
      type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)        :
      type==GRAPH_ELEMENT_TYPE_ELEMENT                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)                  :
      type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)               :
      type==GRAPH_ELEMENT_TYPE_FORM                      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)                     :
      type==GRAPH_ELEMENT_TYPE_WINDOW                    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)                   :
      //--- WinForms
      type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)              :
      type==GRAPH_ELEMENT_TYPE_WF_BASE                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)                  :
      //--- Контейнеры
      type==GRAPH_ELEMENT_TYPE_WF_CONTAINER              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER)             :
      type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_PANEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)           :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)       :
      //--- Стандартные элементы управления
      type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE)           :
      type==GRAPH_ELEMENT_TYPE_WF_LABEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON)           :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON                 ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON)                :
      type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX)     :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM)         :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX)      :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX)       :
      //--- Вспомогательные объекты элементов управления
      type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)            :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD)             :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON)          :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP)       :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT)    :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) :
      type==GRAPH_ELEMENT_TYPE_WF_SPLITTER               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER)              :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+

Метод возвращает соответствующее текстовое сообщение в зависимости от переданного в него типа графического элемента.


Так как у нас появились новые свойства графического элемента, нам необходимо добавить их в структуру объекта для его правильного сохранения в файл и чтения из файла.

В файле \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh впишем в структуру новые свойства:

private:
   int               m_shift_coord_x;                          // Смещение координаты X относительно базового объекта
   int               m_shift_coord_y;                          // Смещение координаты Y относительно базового объекта
   struct SData
     {
      //--- Целочисленные свойства объекта
      int            id;                                       // Идентификатор элемента
      int            type;                                     // Тип графического элемента

      //---...
      //---...

      bool           displayed;                                // Флаг отображения не скрытого элемента управления
      int            split_container_fixed_panel;              // Панель, сохраняющая свои размеры при изменении размера контейнера
      bool           split_container_splitter_fixed;           // Флаг перемещаемости разделителя
      int            split_container_splitter_distance;        // Расстояние от края до разделителя
      int            split_container_splitter_width;           // Толщина разделителя
      int            split_container_splitter_orientation;     // Расположение разделителя
      bool           split_container_panel1_collapsed;         // Флаг свёрнутости панели 1
      int            split_container_panel1_min_size;          // Минимальный размер панели 1
      bool           split_container_panel2_collapsed;         // Флаг свёрнутости панели 2
      int            split_container_panel2_min_size;          // Минимальный размер панели 2
      int            control_area_x;                           // X-координата области управления
      int            control_area_y;                           // Y-координата области управления
      int            control_area_width;                       // Ширина области управления
      int            control_area_height;                      // Высота области управления
      int            scroll_area_x_right;                      // X-координата области прокрутки справа
      int            scroll_area_y_right;                      // Y-координата области прокрутки справа
      int            scroll_area_width_right;                  // Ширина области прокрутки справа
      int            scroll_area_height_right;                 // Высота области прокрутки справа
      int            scroll_area_x_bottom;                     // X-координата области прокрутки снизу
      int            scroll_area_y_bottom;                     // Y-координата области прокрутки снизу
      int            scroll_area_width_bottom;                 // Ширина области прокрутки снизу
      int            scroll_area_height_bottom;                // Высота области прокрутки снизу
      int            border_left_area_width;                   // Ширина области левой грани
      int            border_bottom_area_width;                 // Ширина области нижней грани
      int            border_right_area_width;                  // Ширина области правой грани
      int            border_top_area_width;                    // Ширина области верхней грани
      //--- Вещественные свойства объекта

      //--- Строковые свойства объекта
      uchar          name_obj[64];                             // Имя объекта-графического элемента
      uchar          name_res[64];                             // Имя графического ресурса
      uchar          text[256];                                // Текст графического элемента
      uchar          descript[256];                            // Описание графического элемента
     };
   SData             m_struct_obj;                             // Структура объекта


В публичной секции класса объявим метод, возвращающий положение курсора относительно области управления элемента:

//--- (1) Сохраняет графический ресурс в массив, (2) восстанавливает ресурс из массива
   bool              ResourceStamp(const string source);
   virtual bool      Reset(void);
   
//--- Возвращает положение курсора относительно (1) всего элемента, (2) активной зоны элемента, (3) зоны управления
   bool              CursorInsideElement(const int x,const int y);
   bool              CursorInsideActiveArea(const int x,const int y);
   bool              CursorInsideControlArea(const int x,const int y);

//--- Создаёт элемент
   bool              Create(const long chart_id,
                            const int wnd_num,
                            const int x,
                            const int y,
                            const int w,
                            const int h,
                            const bool redraw=false);

Метод будет возвращать флаг нахождения курсора внутри его области управления, в которой могут быть различные управляющие элементы, в данном случае — разделитель.

Для метода, возвращающего флаг скрытости элемента, добавим пропущенный ранее модификатор константности:

//--- (1) Устанавливает, (2) возвращает флаг отображения не скрытого элемента управления
   void              SetDisplayed(const bool flag)             { this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,flag);                   }
   bool              Displayed(void)                     const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_DISPLAYED);           }

//--- (1) Устанавливает, (2) возвращает тип графического элемента


В публичной секции добавим методы, возвращающие новые свойства объекта, которые сегодня в него были добавлены:

//--- Возвращает координату (1) левого, (2) правого, (3) верхнего, (4) нижнего края активной зоны элемента
   int               ActiveAreaLeft(void)                const { return int(this.CoordX()+this.ActiveAreaLeftShift());                 }
   int               ActiveAreaRight(void)               const { return int(this.RightEdge()-this.ActiveAreaRightShift());             }
   int               ActiveAreaTop(void)                 const { return int(this.CoordY()+this.ActiveAreaTopShift());                  }
   int               ActiveAreaBottom(void)              const { return int(this.BottomEdge()-this.ActiveAreaBottomShift());           }

//--- Возвращает (1) координату X, (2) координату Y, (3) ширину, (4) высоту зоны управления элемента
   int               ControlAreaX(void)                  const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X);       }
   int               ControlAreaY(void)                  const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y);       }
   int               ControlAreaWidth(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH);   }
   int               ControlAreaHeight(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT);  }

//--- Возвращает (1) координату X, (2) координату Y, (3) ширину, (4) высоту области прокрутки справа элемента
   int               ScrollAreaXRight(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT);        }
   int               ScrollAreaYRight(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT);        }
   int               ScrollAreaWidthRight(void)          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT);    }
   int               ScrollAreaHeightRight(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT);   }

//--- Возвращает (1) координату X, (2) координату Y, (3) ширину, (4) высоту области прокрутки снизу элемента
   int               ScrollAreaXBottom(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM);       }
   int               ScrollAreaYBottom(void)             const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM);       }
   int               ScrollAreaWidthBottom(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM);   }
   int               ScrollAreaHeightBottom(void)        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM);  }

//--- Возвращает ширину (1) левой, (2) правой, (3) верхней, (4) нижней области грани элемента
   int               BorderResizeAreaLeft(void)          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH);     }
   int               BorderResizeAreaRight(void)         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH);    }
   int               BorderResizeAreaTop(void)           const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH);      }
   int               BorderResizeAreaBottom(void)        const { return (int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH);   }
   
//--- Возвращает количество цветов, установленных для градиентной заливки (1) основного фона, при нажатии (2), (3) наведении мышки на элемент управления


В конструкторах класса во все новые свойства объекта впишем значения по умолчанию:

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const int      element_id,
                           const int      element_num,
                           const long     chart_id,
                           const int      wnd_num,
                           const string   descript,
                           const int      x,
                           const int      y,
                           const int      w,
                           const int      h,
                           const color    colour,
                           const uchar    opacity,
                           const bool     movable=true,
                           const bool     activity=true,
                           const bool     redraw=false) : m_shadow(false)
  {
   this.SetTypeElement(element_type);
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(colour,true);
   this.SetOpacity(opacity);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,redraw))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Имя графического ресурса
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Идентификатор графика
      this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow());        // Номер подокна графика

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name());            // Имя объекта-элемента
      this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type);                     // Тип графического элемента

      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Высота области видимости
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true);                        // Флаг отображения не скрытого элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);                      // X-координата области управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);                      // Y-координата области управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,0);                  // Ширина области управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,0);                 // Высота области управления
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,0);                 // X-координата области прокрутки справа
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,0);                 // Y-координата области прокрутки справа
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,0);             // Ширина области прокрутки справа
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,0);            // Высота области прокрутки справа
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,0);                // X-координата области прокрутки снизу
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,0);                // Y-координата области прокрутки снизу
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,0);            // Ширина области прокрутки снизу
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,0);           // Высота области прокрутки снизу
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,0);              // Ширина области левой грани
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,0);            // Ширина области нижней грани
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,0);             // Ширина области правой грани
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,0);               // Ширина области верхней грани
      //---
      this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM);  // Принадлежность графического элемента
      this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0);                              // Приоритет графического объекта на получение события нажатия мышки на графике
      this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,FW_NORMAL);                   // Тип толщины шрифта

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,50);                       // Расстояние от края до разделителя
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,4);                           // Толщина разделителя
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,0);                     // Расположение разделителя
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,false);                     // Флаг свёрнутости панели 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,25);                         // Минимальный размер панели 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);                     // Флаг свёрнутости панели 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,25);                         // Минимальный размер панели 2
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+
//| Защищённый конструктор                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           const long    chart_id,
                           const int     wnd_num,
                           const string  descript,
                           const int     x,
                           const int     y,
                           const int     w,
                           const int     h) : m_shadow(false)
  {
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=NULL;
   this.m_element_base=NULL;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetOpacity(0);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,false))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Имя графического ресурса
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Идентификатор графика
      this.SetProperty(CANV_ELEMENT_PROP_WND_NUM,CGBaseObj::SubWindow());        // Номер подокна графика

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,CGBaseObj::Name());            // Имя объекта-элемента
      this.SetProperty(CANV_ELEMENT_PROP_TYPE,element_type);                     // Тип графического элемента

      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Высота области видимости
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true);                        // Флаг отображения не скрытого элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);                      // X-координата области управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);                      // Y-координата области управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,0);                  // Ширина области управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,0);                 // Высота области управления
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,0);                 // X-координата области прокрутки справа
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,0);                 // Y-координата области прокрутки справа
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,0);             // Ширина области прокрутки справа
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,0);            // Высота области прокрутки справа
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,0);                // X-координата области прокрутки снизу
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,0);                // Y-координата области прокрутки снизу
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,0);            // Ширина области прокрутки снизу
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,0);           // Высота области прокрутки снизу
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,0);              // Ширина области левой грани
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,0);            // Ширина области нижней грани
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,0);             // Ширина области правой грани
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,0);               // Ширина области верхней грани
      //---
      this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM);  // Принадлежность графического элемента
      this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0);                              // Приоритет графического объекта на получение события нажатия мышки на графике
      this.SetProperty(CANV_ELEMENT_PROP_BOLD_TYPE,FW_NORMAL);                   // Тип толщины шрифта

      //---...
      //---...

      this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,FRAME_STYLE_NONE);         // Стиль рамки элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP,0);                     // Размер рамки элемента управления сверху

      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,50);                       // Расстояние от края до разделителя
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,4);                           // Толщина разделителя
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,0);                     // Расположение разделителя
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,false);                     // Флаг свёрнутости панели 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,25);                         // Минимальный размер панели 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);                     // Флаг свёрнутости панели 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,25);                         // Минимальный размер панели 2
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+

Всем свойствам по умолчанию устанавливаем нулевые значения. Для свойства "Ориентиция разделителя" нулевое значение означает вертикальное расположение разделителя.


В методе, создающем структуру объекта, запишем в поля структуры значения новых свойств элемента:

//+------------------------------------------------------------------+
//| Создаёт структуру объекта                                        |
//+------------------------------------------------------------------+
bool CGCnvElement::ObjectToStruct(void)
  {
//--- Сохранение целочисленных свойств
   this.m_struct_obj.id=(int)this.GetProperty(CANV_ELEMENT_PROP_ID);                               // Идентификатор элемента
   this.m_struct_obj.type=(int)this.GetProperty(CANV_ELEMENT_PROP_TYPE);                           // Тип графического элемента
   this.m_struct_obj.belong=(int)this.GetProperty(CANV_ELEMENT_PROP_BELONG);                       // Принадлежность графического элемента
   this.m_struct_obj.number=(int)this.GetProperty(CANV_ELEMENT_PROP_NUM);                          // Номер элемента в списке

   //---...
   //---...

   this.m_struct_obj.split_container_splitter_distance=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE);  // Расстояние от края до разделителя
   this.m_struct_obj.split_container_splitter_width=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH);        // Толщина разделителя
   this.m_struct_obj.split_container_splitter_orientation=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION);  // Расположение разделителя
   this.m_struct_obj.split_container_panel1_collapsed=(bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED);   // Флаг свёрнутости панели 1
   this.m_struct_obj.split_container_panel1_min_size=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE);      // Минимальный размер панели 1
   this.m_struct_obj.split_container_panel2_collapsed=(bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED);   // Флаг свёрнутости панели 1
   this.m_struct_obj.split_container_panel2_min_size=(int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE);      // Минимальный размер панели 2
   this.m_struct_obj.control_area_x=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X);                      // X-координата области управления
   this.m_struct_obj.control_area_y=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y);                      // Y-координата области управления
   this.m_struct_obj.control_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH);              // Ширина области управления
   this.m_struct_obj.control_area_height=(int)this.GetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT);            // Высота области управления
   this.m_struct_obj.scroll_area_x_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT);            // X-координата области прокрутки справа
   this.m_struct_obj.scroll_area_y_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT);            // Y-координата области прокрутки справа
   this.m_struct_obj.scroll_area_width_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT);    // Ширина области прокрутки справа
   this.m_struct_obj.scroll_area_height_right=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT);  // Высота области прокрутки справа
   this.m_struct_obj.scroll_area_x_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM);          // X-координата области прокрутки снизу
   this.m_struct_obj.scroll_area_y_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM);          // Y-координата области прокрутки снизу
   this.m_struct_obj.scroll_area_width_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM);  // Ширина области прокрутки снизу
   this.m_struct_obj.scroll_area_height_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM);// Высота области прокрутки снизу
   this.m_struct_obj.border_left_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH);      // Ширина области левой грани
   this.m_struct_obj.border_bottom_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH);  // Ширина области нижней грани
   this.m_struct_obj.border_right_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH);    // Ширина области правой грани
   this.m_struct_obj.border_top_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH);        // Ширина области верхней грани
//--- Сохранение вещественных свойств

//--- Сохранение строковых свойств
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_OBJ),this.m_struct_obj.name_obj);   // Имя объекта-графического элемента
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_NAME_RES),this.m_struct_obj.name_res);   // Имя графического ресурса
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_TEXT),this.m_struct_obj.text);           // Текст графического элемента
   ::StringToCharArray(this.GetProperty(CANV_ELEMENT_PROP_DESCRIPTION),this.m_struct_obj.descript);// Описание графического элемента
   //--- Сохранение структуры в uchar-массив
   ::ResetLastError();
   if(!::StructToCharArray(this.m_struct_obj,this.m_uchar_array))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_SAVE_OBJ_STRUCT_TO_UARRAY,true);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+


В методе, создающем объект из структуры, запишем в свойства объекта значения из соответствующих полей структуры:

//+------------------------------------------------------------------+
//| Создаёт объект из структуры                                      |
//+------------------------------------------------------------------+
void CGCnvElement::StructToObject(void)
  {
//--- Сохранение целочисленных свойств
   this.SetProperty(CANV_ELEMENT_PROP_ID,this.m_struct_obj.id);                                    // Идентификатор элемента
   this.SetProperty(CANV_ELEMENT_PROP_TYPE,this.m_struct_obj.type);                                // Тип графического элемента
   this.SetProperty(CANV_ELEMENT_PROP_BELONG,this.m_struct_obj.belong);                            // Принадлежность графического элемента
   this.SetProperty(CANV_ELEMENT_PROP_NUM,this.m_struct_obj.number);                               // Номер элемента в списке

   //---...
   //---...

   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,this.m_struct_obj.split_container_splitter_distance); // Расстояние от края до разделителя
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,this.m_struct_obj.split_container_splitter_width);       // Толщина разделителя
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,this.m_struct_obj.split_container_splitter_orientation); // Расположение разделителя
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,this.m_struct_obj.split_container_panel1_collapsed);   // Флаг свёрнутости панели 1
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,this.m_struct_obj.split_container_panel1_min_size);     // Минимальный размер панели 1
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,this.m_struct_obj.split_container_panel2_collapsed);   // Флаг свёрнутости панели 1
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,this.m_struct_obj.split_container_panel2_min_size);     // Минимальный размер панели 2
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.m_struct_obj.control_area_x);                        // X-координата области управления
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.m_struct_obj.control_area_y);                        // Y-координата области управления
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.m_struct_obj.control_area_width);                // Ширина области управления
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.m_struct_obj.control_area_height);              // Высота области управления
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,this.m_struct_obj.scroll_area_x_right);              // X-координата области прокрутки справа
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT,this.m_struct_obj.scroll_area_y_right);              // Y-координата области прокрутки справа
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT,this.m_struct_obj.scroll_area_width_right);      // Ширина области прокрутки справа
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT,this.m_struct_obj.scroll_area_height_right);    // Высота области прокрутки справа
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM,this.m_struct_obj.scroll_area_x_bottom);            // X-координата области прокрутки снизу
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM,this.m_struct_obj.scroll_area_y_bottom);            // Y-координата области прокрутки снизу
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM,this.m_struct_obj.scroll_area_width_bottom);    // Ширина области прокрутки снизу
   this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM,this.m_struct_obj.scroll_area_height_bottom);  // Высота области прокрутки снизу
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH,this.m_struct_obj.border_left_area_width);        // Ширина области левой грани
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,this.m_struct_obj.border_bottom_area_width);    // Ширина области нижней грани
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,this.m_struct_obj.border_right_area_width);      // Ширина области правой грани
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,this.m_struct_obj.border_top_area_width);          // Ширина области верхней грани
//--- Сохранение вещественных свойств

//--- Сохранение строковых свойств
   this.SetProperty(CANV_ELEMENT_PROP_NAME_OBJ,::CharArrayToString(this.m_struct_obj.name_obj));   // Имя объекта-графического элемента
   this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,::CharArrayToString(this.m_struct_obj.name_res));   // Имя графического ресурса
   this.SetProperty(CANV_ELEMENT_PROP_TEXT,::CharArrayToString(this.m_struct_obj.text));           // Текст графического элемента
   this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,::CharArrayToString(this.m_struct_obj.descript));// Описание графического элемента
  }
//+------------------------------------------------------------------+


За пределами тела класса напишем реализацию метода, возвращающего положение курсора относительно зоны управления элемента:

//+------------------------------------------------------------------+
//|Возвращает положение курсора относительно зоны управления элемента|
//+------------------------------------------------------------------+
bool CGCnvElement::CursorInsideControlArea(const int x,const int y)
  {
   return(x>=this.ControlAreaX() && x<=this.ControlAreaX()+this.ControlAreaWidth() && y>=this.ControlAreaY() && y<=this.ControlAreaY()+this.ControlAreaHeight());
  }
//+------------------------------------------------------------------+

В метод передаются координаты курсора и возвращается флаг нахождения значений координат X и Y курсора в пределах размеров области управления, установленных для объекта.


В файле класса объекта-формы \MQL5\Include\DoEasy\Objects\Graph\Form.mqh в методе, показывающем форму, добавим проверку для прикреплённых объектов на то, что для объекта установлен флаг его отображения и, если объект не должен отображаться, то и показывать его не нужно:

//+------------------------------------------------------------------+
//| Показывает форму                                                 |
//+------------------------------------------------------------------+
void CForm::Show(void)
  {
//--- Если элемент не должен показываться (скрыт внутри другого элемента управления) - уходим
   if(!this.Displayed())
      return;
//--- Если у объекта есть тень - отобразим её
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Show();
//--- Отобразим главную форму
   CGCnvElement::Show();
//--- В цикле по всем привязанным графическим объектам
   for(int i=0;i<this.m_list_elements.Total();i++)
     {
      //--- получим очередной графический элемент
      CGCnvElement *element=this.m_list_elements.At(i);
      if(element==NULL || !element.Displayed())
         continue;
      //--- и отобразим его
      element.Show();
     }
//--- Обновим форму
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+


В методе, устанавливающем и возвращающем состояние мышки относительно формы, впишем блок кода, заполняющий бит переменной m_mouse_state_flags, отвечающий за расположение курсора внутри области управления:

//+------------------------------------------------------------------+
//| Устанавливает и возвращает состояние мышки относительно формы    |
//+------------------------------------------------------------------+
ENUM_MOUSE_FORM_STATE CForm::MouseFormState(const int id,const long lparam,const double dparam,const string sparam)
  {
//--- Получаем состояние мышки относительно формы и состояние кнопок мыши и клавиш Shift и Ctrl
   this.m_mouse_form_state=MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED;
   ENUM_MOUSE_BUTT_KEY_STATE state=this.m_mouse.ButtonKeyState(id,lparam,dparam,sparam);
//--- Получаем флаги состоянии мышки из объекта класса CMouseState и сохраняем их в переменной
   this.m_mouse_state_flags=this.m_mouse.GetMouseFlags();
//--- Если курсор внутри формы
   if(CGCnvElement::CursorInsideElement(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
     {
      //--- Устанавливаем бит 8, отвечающий за флаг "курсор внутри формы"
      this.m_mouse_state_flags |= (0x0001<<8);
      //--- Если курсор внутри активной зоны - устанавливаем бит 9 "курсор внутри активной зоны"
      if(CGCnvElement::CursorInsideActiveArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<9);
      //--- иначе - снимаем бит "курсор внутри активной зоны"
      else this.m_mouse_state_flags &=0xFDFF;
      //--- Если курсор внутри области управления - устанавливаем бит 10 "курсор внутри области управления"
      if(CGCnvElement::CursorInsideControlArea(this.m_mouse.CoordX(),this.m_mouse.CoordY()))
         this.m_mouse_state_flags |= (0x0001<<10);
      //--- иначе - снимаем бит "курсор внутри области управления"
      else this.m_mouse_state_flags &=0xFBFF;
      //--- Если нажата одна из трёх кнопок мыши - проверяем расположение курсора в активной области и
      //--- возвращаем соответствующее значение нажатой кнопки (в активной зоне или в области формы)
      if((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0)
         this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED : MOUSE_FORM_STATE_INSIDE_FORM_PRESSED);
      //--- иначе если ни одна кнопка мышки не нажата
      else
        {
         //--- если колесо мышки прокручивается, возвращаем соответствующее значение прокрутки колеса (в активной зоне или в области формы)
         if((this.m_mouse_state_flags & 0x0080)!=0)
            this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL : MOUSE_FORM_STATE_INSIDE_FORM_WHEEL);
         //--- иначе - возвращаем соответствующее значение ненажатой кнопки (в активной зоне или в области формы)
         else
            this.m_mouse_form_state=((this.m_mouse_state_flags & 0x0200)!=0 ? MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED : MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED);
        } 
     }
//--- Если курсор снаружи формы
   else
     {
      //--- возвращаем соответствующее значение кнопок в неактивной зоне
      this.m_mouse_form_state=
        (
         ((this.m_mouse_state_flags & 0x0001)!=0 || (this.m_mouse_state_flags & 0x0002)!=0 || (this.m_mouse_state_flags & 0x0010)!=0) ? 
          MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED : MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED
        );
     }
   return this.m_mouse_form_state;
  }
//+------------------------------------------------------------------+

Если метод CursorInsideControlArea() вернул true, это означает, что курсор находится в области управления формы, и в этом случае нужно установить бит 10 (выставить в 1), сигнализирующий об этом. Если же курсор не в области управления, то бит 10 снимается (выставляется в ноль).


Обработчик событий мышки просто сократим, написав все кейсы оператора switch в одну строку:

//+------------------------------------------------------------------+
//| Обработчик событий мышки                                         |
//+------------------------------------------------------------------+
void CForm::OnMouseEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
   switch(id)
     {
      //--- Курсор за пределами формы, кнопки мышки не нажаты
      //--- Курсор за пределами формы, нажата кнопка мышки (любая)
      //--- Курсор за пределами формы, прокручивается колёсико мышки
      case MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED :
      case MOUSE_EVENT_OUTSIDE_FORM_PRESSED     :
      case MOUSE_EVENT_OUTSIDE_FORM_WHEEL       :
        break;
      //--- Курсор в пределах формы, кнопки мышки не нажаты
      case MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED        :  this.MouseInsideNotPressedHandler(id,lparam,dparam,sparam);    break;
      //--- Курсор в пределах формы, нажата кнопка мышки (любая)
      case MOUSE_EVENT_INSIDE_FORM_PRESSED            :  this.MouseInsidePressedHandler(id,lparam,dparam,sparam);       break;
      //--- Курсор в пределах формы, прокручивается колёсико мышки
      case MOUSE_EVENT_INSIDE_FORM_WHEEL              :  this.MouseInsideWhellHandler(id,lparam,dparam,sparam);         break;
      //--- Курсор в пределах активной области, кнопки мышки не нажаты
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED :  this.MouseActiveAreaNotPressedHandler(id,lparam,dparam,sparam);break;
      //--- Курсор в пределах активной области, нажата кнопка мышки (любая)
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_PRESSED     :  this.MouseActiveAreaPressedHandler(id,lparam,dparam,sparam);   break;
      //--- Курсор в пределах активной области, прокручивается колёсико мышки
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_WHEEL       :  this.MouseActiveAreaWhellHandler(id,lparam,dparam,sparam);     break;
      //--- Курсор в пределах активной области, отжата кнопка мышки (левая)
      case MOUSE_EVENT_INSIDE_ACTIVE_AREA_RELEASED    :  this.MouseActiveAreaReleasedHandler(id,lparam,dparam,sparam);  break;
      //--- Курсор в пределах области прокрутки окна, кнопки мышки не нажаты
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_NOT_PRESSED :  this.MouseScrollAreaNotPressedHandler(id,lparam,dparam,sparam);break;
      //--- Курсор в пределах области прокрутки окна, нажата кнопка мышки (любая)
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_PRESSED     :  this.MouseScrollAreaPressedHandler(id,lparam,dparam,sparam);   break;
      //--- Курсор в пределах области прокрутки окна, прокручивается колёсико мышки
      case MOUSE_EVENT_INSIDE_SCROLL_AREA_WHEEL       :  this.MouseScrollAreaWhellHandler(id,lparam,dparam,sparam);     break;
      //---MOUSE_EVENT_NO_EVENT
      default: break;
     }
   this.m_mouse_event_last=(ENUM_MOUSE_EVENT)id;
  }
//+------------------------------------------------------------------+

Таким образом метод просто становится лучше читаемым — его весь сразу можно окинуть взглядом. Впрочем, это из области личных предпочтений и привычек.

В обработчик последнего события мышки добавим заготовки для обработки новых "прошлых" состояний:

//+------------------------------------------------------------------+
//| Обработчик последнего события мышки                              |
//+------------------------------------------------------------------+
void CForm::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {
      //--- Курсор за пределами формы, кнопки мышки не нажаты
      //--- Курсор за пределами формы, нажата кнопка мышки (любая)
      //--- Курсор за пределами формы, прокручивается колёсико мышки
      case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED        :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED            :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL              :
      case MOUSE_FORM_STATE_NONE                            :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED  || 
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED         || 
           this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED        ||
           this.MouseEventLast()==MOUSE_EVENT_NO_EVENT)
          {
           this.SetBackgroundColor(this.BackgroundColorInit(),false);
           this.SetBorderColor(this.BorderColorInit(),false);
           this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
          }
        break;
      //--- Курсор в пределах формы, кнопки мышки не нажаты
      //--- Курсор в пределах формы, нажата кнопка мышки (любая)
      //--- Курсор в пределах формы, прокручивается колёсико мышки
      //--- Курсор в пределах активной области, кнопки мышки не нажаты
      //--- Курсор в пределах активной области, нажата кнопка мышки (любая)
      //--- Курсор в пределах активной области, прокручивается колёсико мышки
      //--- Курсор в пределах активной области, отжата кнопка мышки (левая)
      //--- Курсор в пределах области прокрутки окна, кнопки мышки не нажаты
      //--- Курсор в пределах области прокрутки окна, нажата кнопка мышки (любая)
      //--- Курсор в пределах области прокрутки окна, прокручивается колёсико мышки
      //--- Курсор в пределах области изменения размеров окна, кнопки мышки не нажаты
      //--- Курсор в пределах области изменения размеров окна, нажата кнопка мышки (любая)
      //--- Курсор в пределах области изменения размеров окна, прокручивается колёсико мышки
      //--- Курсор в пределах области резделителя окна, кнопки мышки не нажаты
      //--- Курсор в пределах области резделителя окна, нажата кнопка мышки (любая)
      //--- Курсор в пределах области резделителя окна, прокручивается колёсико мышки
      case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED         :
      case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED             :
      case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL               :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED     :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_NOT_PRESSED:
      case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_PRESSED    :
      case MOUSE_FORM_STATE_INSIDE_SPLITTER_AREA_WHEEL      :
        break;
      //---MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

В последующем нам может потребоваться обработать эти события, которые были последними у мышки относительно объекта, поэтому сразу же добавили сюда заготовки обработчиков этих событий. На данный момент они никак не обрабатываются.


В файле класса базового WinForms-объекта \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh в методе, перерисовывающем объект, впишем проверку на флаг отображения объекта и, если объект отображать не нужно, то и перерисовывать его не надо:

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CWinFormBase::Redraw(bool redraw)
  {
//--- Если тип объекта меньше, чем "Базовый WinForms-объект" - уходим
   if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || !this.Displayed())
      return;
//--- Получим объект "Тень"
   CShadowObj *shadow=this.GetShadowObj();
//--- Если у объекта есть тень, и объект "Тень" существует - перерисуем тень
   if(this.IsShadow() && shadow!=NULL)
     {
      //--- сотрём ранее нарисованную тень,
      shadow.Erase();
      //--- запомним относительные координаты тени,
      int x=shadow.CoordXRelative();
      int y=shadow.CoordYRelative();
      //--- перерисуем тень,
      if(redraw)
         shadow.Draw(0,0,shadow.Blur(),redraw);
      //--- восстановим относительные координаты тени
      shadow.SetCoordXRelative(x);
      shadow.SetCoordYRelative(y);
     }
//--- Если стоит флаг перерисовки
   if(redraw)
     {
      //--- полностью перерисовываем объект и запоминаем его новый изначальный вид
      this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,redraw);
      this.Done();
     }
//--- иначе - стираем объект
   else
      this.Erase();
//--- Перерисуем с флагом перерисовки все прикреплённые объекты
   for(int i=0;i<this.ElementsTotal();i++)
     {
      CWinFormBase *element=this.GetElement(i);
      if(element==NULL)
         continue;
      if(redraw)
         element.Redraw(redraw);
     }
//--- При установленном флаге перерисовки, и если это главный объект, к которому прикреплены остальные -
//--- перерисуем чарт для немедленного отображения изменений
   if(redraw && this.GetMain()==NULL)
      ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+


В методе, возвращающем описание целочисленного свойства элемента, добавим блок кода для возврата описаний новых свойств графического элемента:

//+------------------------------------------------------------------+
//| Возвращает описание целочисленного свойства элемента             |
//+------------------------------------------------------------------+
string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_INTEGER property,bool only_prop=false)
  {
   return
     (
      property==CANV_ELEMENT_PROP_ID                           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ID)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TYPE                         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TYPE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+this.TypeElementDescription()
         )  :

      //---...
      //---...

      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_X               ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_X)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_Y               ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_Y)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT      ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT     ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_X_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_Y_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM     ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_WIDTH_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SCROLL_AREA_HEIGHT_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH       ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_LEFT_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH     ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH      ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH        ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED   ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED   ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

В зависимости от переданного в метод свойства, создаётся и возвращается строка описания. Если свойство не поддерживается объектом, то вместо значения свойства выводится запись о том, что свойство не поддерживается. В зависимости от флага only_prop, либо выводится просто наименование свойства, либо вместе с присвоенным ему значением.

Теперь мы можем приступить к созданию нового объекта библиотеки.


Класс вспомогательного объекта-разделителя

Вспомогательные объекты библиотеки сами по себе не являются полноценными элементами управления, но используются для построения таких объектов. Объект-разделитель нам нужен для сигнализации о том, что мы можем переместить область, разделяющую две панели в элементе управления SplitContainer. Перемещением этого объекта мышкой мы вызовем обработчик события элемента  SplitContainer, где будет обработано это событие и изменены размеры панелей. В то же время, такой объект нам может потребоваться для взаимодействия и с другими элементами управления, поэтому он будет находиться в папке вспомогательных объектов и использоваться в элементах управления, в которых потребуется для реализации их функционала.

В папке \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ создадим новый файл Splitter.mqh класса CSplitter.

Класс должен быть унаследован от класса базового WinForms-объекта библиотеки, и его файл должен быть подключен к файлу создаваемого класса:

//+------------------------------------------------------------------+
//|                                                     Splitter.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\WinFormBase.mqh"
//+------------------------------------------------------------------+
//| Класс объекта Splitter элементов управления WForms               |
//+------------------------------------------------------------------+
class CSplitter : public CWinFormBase
  {
  }


В защищённой секции класса объявим виртуальный метод, рисующий сетку (штриховку объекта) и защищённый конструктор. В публичной секции класса объявим параметрический конструктор и методы для перерисовки и очистки фона графического элемента:

//+------------------------------------------------------------------+
//| Класс объекта Splitter элементов управления WForms               |
//+------------------------------------------------------------------+
class CSplitter : public CWinFormBase
  {
private:

protected:
   //--- Рисует сетку
   virtual void      DrawGrid(void);
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CSplitter(const ENUM_GRAPH_ELEMENT_TYPE type,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
public:
//--- Конструктор
                     CSplitter(const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
//--- Перерисовывает объект
   virtual void      Redraw(bool redraw);
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Очищает элемент заливкой градиентом
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
  };
//+------------------------------------------------------------------+

Метод, рисующий сетку объекта, объявлен виртуальным на случай, если нужно будет делать производные классы с иной отрисовкой.

Рассмотрим объявленные методы подробнее.


Защищённый конструктор класса:

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CSplitter::CSplitter(const ENUM_GRAPH_ELEMENT_TYPE type,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CWinFormBase(type,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(0);
  }
//+------------------------------------------------------------------+

В формальных параметрах конструктора передаётся тип создаваемого объекта, который в строке инициализации передаётся в конструктор родительского класса. В теле класса устанавливается переданный в конструктор тип графического элемента и тип графического объекта библиотеки как вспомогательный объект. Значения Padding, Margin и размеры рамки выставляются в ноль.


Параметрический конструктор:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CSplitter::CSplitter(const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CWinFormBase(GRAPH_ELEMENT_TYPE_WF_SPLITTER,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(0);
   this.SetDisplayed(false);
  }
//+------------------------------------------------------------------+

Здесь всё аналогично защищённому конструктору, но тип графического элемента указывается напрямую, а не передаётся в формальных параметрах.


Метод, перерисовывающий объект:

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CSplitter::Redraw(bool redraw)
  {
//--- Если элемент не должен показываться (скрыт внутри другого элемента управления) - уходим
   if(!this.Displayed())
      return;
//--- Заполняем объект цветом фона с прозрачностью
   this.Erase(this.BackgroundColor(),this.Opacity(),true);
  }
//+------------------------------------------------------------------+

Если для объекта снят флаг его отображения на видимом и доступном для взаимодействия элементе управления, то перерисовывать объект не нужно — уходим из метода. Если флаг отображения установлен, то вызываем метод заполнения объекта цветом.


Методы, очищающие элемент с заполнением его цветом и непрозрачностью:

//+------------------------------------------------------------------+
//| Очищает элемент с заполнением его цветом и непрозрачностью       |
//+------------------------------------------------------------------+
void CSplitter::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Если элемент не должен показываться (скрыт внутри другого элемента управления) - уходим
   if(!this.Displayed())
      return;
//--- Закрашиваем элемент с указанным цветом и флагом необходимости перерисовки
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- Рисуем сетку
   this.DrawGrid();
//--- Обрезаем и обновляем элемент с указанным флагом необходимости перерисовки
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Очищает элемент заливкой градиентом                              |
//+------------------------------------------------------------------+
void CSplitter::Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Если элемент не должен показываться (скрыт внутри другого элемента управления) - уходим
   if(!this.Displayed())
      return;
//--- Закрашиваем элемент с указанным массивом цветов и флагом необходимости перерисовки
   CGCnvElement::EraseNoCrop(colors,opacity,vgradient,cycle,false);
//--- Рисуем сетку
   this.DrawGrid();
//--- Обрезаем и обновляем элемент с указанным флагом необходимости перерисовки
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Логика методов полностью прокомментирована в коде. Первый метод заполняет фон одним цветом, второй — градиентом.


Метод, рисующий сетку:

//+------------------------------------------------------------------+
//| Рисует сетку                                                     |
//+------------------------------------------------------------------+
void CSplitter::DrawGrid(void)
  {
   for(int y=0;y<this.Height()-1;y++)
      for(int x=0;x<this.Width();x++)
         this.SetPixel(x,y,this.ForeColor(),uchar(y%2==0 ? (x%2==0 ? 255 : 0) : (x%2==0 ? 0 : 255)));
  }
//+------------------------------------------------------------------+

Нам необходимо заполнить фон объекта точками в шахматном порядке. Для этого организуем два цикла: цикл по строкам и цикл по столбцам.

Таким образом мы заполняем весь фон точками, расположенными в шахматном порядке.

Это всё, что необходмо на данном этапе для работы класса объекта-разделителя.

Теперь необходимо подключить его к классу элемента управления SplitContainer, создать его и управлять им.

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh, в приватной секции класса объявим переменные для хранения координат и размеров панелей и разделителя, и объявим метод для установки параметров панелей:

//+------------------------------------------------------------------+
//|                                               SplitContainer.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "..\Helpers\SplitContainerPanel.mqh"
#include "..\Helpers\Splitter.mqh"
//+------------------------------------------------------------------+
//| Класс объекта-элемента управления WForms SplitContainer          |
//+------------------------------------------------------------------+
class CSplitContainer : public CContainer
  {
private:
   int               m_panel1_x;             // X-координата панели1
   int               m_panel1_y;             // Y-координата панели1
   int               m_panel1_w;             // ширина панели1
   int               m_panel1_h;             // высота панели1
   int               m_panel2_x;             // X-координата панели2
   int               m_panel2_y;             // Y-координата панели2
   int               m_panel2_w;             // ширина панели2
   int               m_panel2_h;             // высота панели2
   int               m_splitter_x;           // X-координата разделителя
   int               m_splitter_y;           // Y-координата разделителя
   int               m_splitter_w;           // ширина разделителя
   int               m_splitter_h;           // высота разделителя
//--- Создаёт новый графический объект
   virtual CGCnvElement   *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                           const int element_num,
                                           const string descript,
                                           const int x,
                                           const int y,
                                           const int w,
                                           const int h,
                                           const color colour,
                                           const uchar opacity,
                                           const bool movable,
                                           const bool activity);
//--- Устанавливает параметры панелям
   bool              SetsPanelParams(void);
                                           
public:


В публичной секции класса объявим/напишем новые методы, у методов получения указателей на панели изменим возвращаемый тип (из-за ошибок компиляции при отдельной компиляции файла класса CSplitContainerPanel), а реализацию некоторых методов вынесем за пределы класса, оставив здесь только объявление методов:

public:
//--- Создаёт панели
   void              CreatePanels(void);

//--- Возвращает указатель на указанную панель
   CWinFormBase     *GetPanel(const int index)                 { return CForm::GetElement(index);  }
   
//--- Возвращает указатель на (1) панель1, (2) панель2
   CWinFormBase     *GetPanel1(void)                           { return this.GetPanel(0);          }
   CWinFormBase     *GetPanel2(void)                           { return this.GetPanel(1);          }
   
//--- Возвращает с указанной панели элемент (1) по индексу, (2) по типу и индексу, (3) по имени
   CGCnvElement     *GetPanelElement(const int panel,const int index);
   CGCnvElement     *GetPanelElementByType(const int panel,const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   CGCnvElement     *GetPanelElementByName(const int panel,const string name);
   
//--- Возвращает указатель на разделитель
   CSplitter        *GetSplitter(void)                         { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SPLITTER,0);                      }
   
//--- (1) устанавливает, (2) возвращает минимально-возможный размер панели 1 и 2
   void              SetPanel1MinSize(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE,value);           }
   int               Panel1MinSize(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_MIN_SIZE);     }
   void              SetPanel2MinSize(const int value)         { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,value);           }
   int               Panel2MinSize(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE);     }
   
//--- (1) устанавливает, (2) возвращает флаг свёрнутости панели 1
   void              SetPanel1Collapsed(const int flag);
   bool              Panel1Collapsed(void)               const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED);   }
//--- (1) устанавливает, (2) возвращает флаг свёрнутости панели 2
   void              SetPanel2Collapsed(const int flag);
   bool              Panel2Collapsed(void)               const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED);   }
   
//--- (1) устанавливает, (2) возвращает дистанцию разделителя от края
   void              SetSplitterDistance(const int value);
   int               SplitterDistance(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE);   }
   
//--- (1) устанавливает, (2) возвращает флаг неперемещаемости разделителя
   void              SetSplitterFixed(const bool flag)         { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED,flag);             }
   bool              SplitterFixed(void)                 const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_FIXED);     }
   
//--- (1) устанавливает, (2) возвращает толщину разделителя
   void              SetSplitterWidth(const int value);
   int               SplitterWidth(void)                 const { return (int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH);      }
   
//--- (1) устанавливает, (2) возвращает расположение разделителя
   void              SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value)
                       { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value);                                              }
   ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION SplitterOrientation(void) const
                       { return(ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION);      }
   
//--- (1) устанавливает, (2) возвращает панель, не изменяющую свои размеры при изменении размеров контейнера
   void              SetFixedPanel(const ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL value)
                       { this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL,value);                                                       }
   ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL FixedPanel(void) const
                       { return(ENUM_CANV_ELEMENT_SPLIT_CONTAINER_FIXED_PANEL)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_FIXED_PANEL);        }
   
//--- Создаёт новый присоединённый элемент на указанной панели
   bool              CreateNewElement(const int panel_index,
                                      const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
   
//--- Обработчик событий
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Обработчик события  Курсор в пределах активной области, кнопки мышки не нажаты
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Конструктор
                     CSplitContainer(const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
  };
//+------------------------------------------------------------------+


В методе, создающем новый графический объект, добавим ещё один тип объекта, который может быть создан:

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CSplitContainer::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                                const int obj_num,
                                                const string descript,
                                                const int x,
                                                const int y,
                                                const int w,
                                                const int h,
                                                const color colour,
                                                const uchar opacity,
                                                const bool movable,
                                                const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL : element=new CSplitContainerPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER              : element=new CSplitter(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      default  :  break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

Так как теперь нам нужно внутри объекта кроме панелей создать ещё и разделитель, то метод должен уметь это делать — для создания нового объекта-разделителя и служит добавленная строка.


В методе, создающем панели, добавим блок кода для создания объекта-разделителя:

//+------------------------------------------------------------------+
//| Создаёт панели                                                   |
//+------------------------------------------------------------------+
void CSplitContainer::CreatePanels(void)
  {
   this.m_list_elements.Clear();
   if(this.SetsPanelParams())
     {
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel1_x,this.m_panel1_y,this.m_panel1_w,this.m_panel1_h,clrNONE,255,true,false))
         return;
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel2_x,this.m_panel2_y,this.m_panel2_w,this.m_panel2_h,clrNONE,255,true,false))
         return;
      //---
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false))
         return;
      CSplitter *splitter=this.GetSplitter();
      if(splitter!=NULL)
        {
         splitter.SetMovable(true);
         splitter.SetDisplayed(false);
         splitter.Hide();
        }
     }
  }
//+------------------------------------------------------------------+

Здесь: если не удалось создать объект-разделитель — уходим из метода. Далее получаем указатель на созданный объект-разделитель, устанавливаем ему флаг перемещаемости (его же нужно будет смещать мышкой), устанавливаем флаг того, что его не нужно отображать и скрываем созданный объект.
Создание панелей тоже изменено. Теперь мы сначала устанавливаем параметры панелям в новом методе SetsPanelParams(), который будет рассмотрен ниже, и в котором в зависимости от расположения разделителя и флагов свёрнутости панелей устанавливаются их начальные координаты и размеры, записываемые в новые, специально для этого предназначенные переменные, значения которых и передаются в методы создания панелей.


Метод, устанавливающий параметры панелям:

//+------------------------------------------------------------------+
//| Устанавливает параметры панелям                                  |
//+------------------------------------------------------------------+
bool CSplitContainer::SetsPanelParams(void)
  {
   switch(this.SplitterOrientation())
     {
      //--- Разделитель расположен вертикально
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL    :
        //--- Если обе панели не свёрнуты
        if(!this.Panel1Collapsed() && !this.Panel2Collapsed())
          {
           //--- записываем координаты и размеры панели1
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.SplitterDistance();
           this.m_panel1_h=this.Height();
           //--- записываем координаты и размеры панели2
           this.m_panel2_x=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width()-this.m_panel2_x;
           this.m_panel2_h=this.Height();
           //--- записываем координаты и размеры разделителя
           this.m_splitter_x=this.SplitterDistance();
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        //--- Если свёрнута панель2
        else if(this.Panel2Collapsed())
          {
           //--- записываем координаты и размеры панели1
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.Height();
           //--- записываем координаты и размеры панели2
           this.m_panel2_x=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width()-this.m_panel2_x;
           this.m_panel2_h=this.Height();
           //--- записываем координаты и размеры разделителя
           this.m_splitter_x=-this.SplitterWidth();
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        //--- Если свёрнута панель1
        else if(this.Panel1Collapsed())
          {
           //--- записываем координаты и размеры панели1
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.SplitterDistance();
           this.m_panel1_h=this.Height();
           //--- записываем координаты и размеры панели2
           this.m_panel2_x=0;
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height();
           //--- записываем координаты и размеры разделителя
           this.m_splitter_x=-this.SplitterWidth();
           this.m_splitter_y=0;
           this.m_splitter_w=this.SplitterWidth();
           this.m_splitter_h=this.Height();
          }
        break;
      //--- Разделитель расположен горизонтально
      case CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL  :
        if(!this.Panel1Collapsed() && !this.Panel2Collapsed())
          {
           //--- записываем координаты и размеры панели1
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.SplitterDistance();
           //--- записываем координаты и размеры панели2
           this.m_panel2_x=0;
           this.m_panel2_y=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height()-this.m_panel2_y;
           //--- записываем координаты и размеры разделителя
           this.m_splitter_x=0;
           this.m_splitter_y=this.SplitterDistance();
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        //--- Если свёрнута панель2
        else if(this.Panel2Collapsed())
          {
           //--- записываем координаты и размеры панели1
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.Height();
           //--- записываем координаты и размеры панели2
           this.m_panel2_x=0;
           this.m_panel2_y=this.SplitterDistance()+this.SplitterWidth();
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height()-this.m_panel2_y;
           //--- записываем координаты и размеры разделителя
           this.m_splitter_x=0;
           this.m_splitter_y=-this.SplitterDistance();
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        //--- Если свёрнута панель1
        else if(this.Panel1Collapsed())
          {
           //--- записываем координаты и размеры панели1
           this.m_panel1_x=0;
           this.m_panel1_y=0;
           this.m_panel1_w=this.Width();
           this.m_panel1_h=this.SplitterDistance();
           //--- записываем координаты и размеры панели2
           this.m_panel2_x=0;
           this.m_panel2_y=0;
           this.m_panel2_w=this.Width();
           this.m_panel2_h=this.Height();
           //--- записываем координаты и размеры разделителя
           this.m_splitter_x=0;
           this.m_splitter_y=-this.SplitterDistance();
           this.m_splitter_w=this.Width();
           this.m_splitter_h=this.SplitterWidth();
          }
        break;
      default:
        return false;
        break;
     }
//--- Устанавливаем координаты и размеры области управления, равными свойствам, установленным разделителю
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.m_splitter_x);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.m_splitter_y);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.m_splitter_w);
   this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.m_splitter_h);
   return true;
  }
//+------------------------------------------------------------------+

В зависимости от расположения разделителя (вертикально или горизонтально) и в зависимости от того не свёрнуты ли обе панели, либо свёрнута какая-то одна из них, записываем в служебные переменные координаты и размеры панелей и разделителя. В конце метода параметры разделителя, установленные в методе, записываем в свойства координат и размеров области управления.


Метод, устанавливающий флаг свёрнутости панели 1:

//+------------------------------------------------------------------+
//| Устанавливает флаг свёрнутости панели 1                          |
//+------------------------------------------------------------------+
void CSplitContainer::SetPanel1Collapsed(const int flag)
  {
//--- Записываем в свойство объекта переданный в метод флаг
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL1_COLLAPSED,flag);
//--- Если панель1 должна быть свёрнута
   if(this.Panel1Collapsed())
     {
      //--- устанавливаем для панели2 флаг того, что она развёрнута
      this.SetPanel2Collapsed(false);
      //--- Если указатель на панель1 получен
      if(this.GetPanel1()!=NULL)
        {
         //--- устанавливаем флаг неотображения панели и скрываем её
         this.GetPanel1().SetDisplayed(false);
         this.GetPanel1().Hide();
        }
      //--- Если указатель на панель2 получен
      if(this.GetPanel2()!=NULL)
        {
         //--- устанавливаем флаг отображения панели, отображаем её и выводим на передний план
         this.GetPanel2().SetDisplayed(true);
         this.GetPanel2().Show();
         this.GetPanel2().BringToTop();
        }
     }
  }
//+------------------------------------------------------------------+

Метод полностью прокомментирован в коде. Помимо того, что мы устанавливаем флаг свёрнутости панели (если в метод передано значение false), мы её к тому же скрываем и устанавливаем для неё флаг неотображения. А панель 2 при этом отображаем с установкой флага отображения и выводим на передний план.

Метод, устанавливающий флаг свёрнутости панели 2:

//+------------------------------------------------------------------+
//| Устанавливает флаг свёрнутости панели 2                          |
//+------------------------------------------------------------------+
void CSplitContainer::SetPanel2Collapsed(const int flag)
  {
//--- Записываем в свойство объекта переданный в метод флаг
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,flag);
//--- Если панель2 должна быть свёрнута
   if(Panel2Collapsed())
     {
      //--- устанавливаем для панели1 флаг того, что она развёрнута
      this.SetPanel1Collapsed(false);
      //--- Если указатель на панель2 получен
      if(this.GetPanel2()!=NULL)
        {
         //--- устанавливаем флаг неотображения панели и скрываем её
         this.GetPanel2().SetDisplayed(false);
         this.GetPanel2().Hide();
        }
      //--- Если указатель на панель1 получен
      if(this.GetPanel1()!=NULL)
        {
         //--- устанавливаем флаг отображения панели, отображаем её и выводим на передний план
         this.GetPanel1().SetDisplayed(true);
         this.GetPanel1().Show();
         this.GetPanel1().BringToTop();
        }
     }
  }
//+------------------------------------------------------------------+

Логика метода аналогична вышерассмотренному, но применяется к панели 2.


Метод, устанавливающий дистанцию разделителя от края:

//+------------------------------------------------------------------+
//| Устанавливает дистанцию разделителя от края                      |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterDistance(const int value)
  {
//--- Записываем в свойство объекта переданное в метод значение
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_DISTANCE,value);
//--- В зависимости от ориентации разделителя (вертикально или горизонтально)
//--- записываем значения в координаты области управления объекта
   switch(this.SplitterOrientation())
     {
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,this.SplitterDistance());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      default:
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,this.SplitterDistance());
        break;
     }
  }
//+------------------------------------------------------------------+

Так как от расположения разделителя зависит начало координат области управления объекта, то в зависимости от ориентации разделителя записываем в область управления координаты разделителя — он является физическим отображением этой виртуальной области.


Метод, устанавливающий толщину разделителя:

//+------------------------------------------------------------------+
//| Устанавливает толщину разделителя                                |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterWidth(const int value)
  {
//--- Записываем в свойство объекта переданное в метод значение
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_WIDTH,value);
//--- В зависимости от ориентации разделителя (вертикально или горизонтально)
//--- записываем значения в свойства ширины и высоты области управления объекта
   switch(this.SplitterOrientation())
     {
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.SplitterWidth());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.Height());
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      default:
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_WIDTH,this.Width());
        this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,this.SplitterWidth());
        break;
     }
  }
//+------------------------------------------------------------------+

Метод аналогичен вышерассмотренному. В зависимости от ориентации разделителя записываем в область управления размеры разделителя, так он физически отображает эту виртуальную область.

Обработчик событий:

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CSplitContainer::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Корректируем смещение по Y для подокна
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- Если идентификатор события - перемещение разделителя
   if(id==WF_CONTROL_EVENT_SPLITTER_MOVE)
     {
      //--- Получаем указатель на объект-разделитель
      CSplitter *splitter=this.GetSplitter();
      if(splitter==NULL)
         return;
      //--- Объявляем переменные для координат разделителя
      int x=(int)lparam;
      int y=(int)dparam;
      //--- В зависимости от ориентации разделителя
      switch(this.SplitterOrientation())
        {
         //--- вертикальное положение разделителя
         case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
           //--- Устанавливаем координату Y равной координате Y элемента управления
           y=this.CoordY();
           //--- Корректируем координату X так, чтобы разделитель не выходил за пределы элемента управления
           //--- с учётом получающейся в итоге минимальной ширины панелей
           if(x<this.CoordX()+this.Panel1MinSize())
              x=this.CoordX()+this.Panel1MinSize();
           if(x>this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth())
              x=this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth();
           break;
         //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
         //--- горизонтальное положение разделителя
         default:
           //--- Устанавливаем координату X равной координате X элемента управления
           x=this.CoordX();
           //--- Корректируем координату Y так, чтобы разделитель не выходил за пределы элемента управления
           //--- с учётом получающейся в итоге минимальной высоты панелей
           if(y<this.CoordY()+this.Panel1MinSize())
              y=this.CoordY()+this.Panel1MinSize();
           if(y>this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth())
              y=this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth();
           break;
        }
      //--- Если разделитель смещён на рассчитанные координаты
      if(splitter.Move(x,y,true))
        {
         //--- устанавливаем разделителю его относительные координаты
         splitter.SetCoordXRelative(splitter.CoordX()-this.CoordX());
         splitter.SetCoordYRelative(splitter.CoordY()-this.CoordY());
         //--- Получаем указатели на обе панели
         CSplitContainerPanel *p1=this.GetPanel1();
         CSplitContainerPanel *p2=this.GetPanel2();
         if(p1==NULL || p2==NULL)
            return;
         //--- В зависимости от ориентации разделителя устанавливаем его новые координаты
         this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY());
         //--- Устанавливаем новые координаты и размеры панелей в зависимости от координат разделителя
         if(this.SetsPanelParams())
           {
            //--- Если размеры панели 1 успешно изменены
            if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
              {
               //--- Если координаты панели 2 изменены на новые
               if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true))
                 {
                  //--- если размеры панели 2 успешно изменены
                  if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
                    {
                     //--- устанавливаем новые относительные координаты панели 2
                     p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
                     p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
                    }
                 }
              }
           }
        }
     }
  }
//+------------------------------------------------------------------+

Логика метода полностью расписана в комментариях к коду. Вкратце: обработчик получает идентификатор события "Перемещение разделителя" и рассчитывает новые координаты и размеры панелей. Панель1 всегда остаётся на своих координатах и лишь изменяет размер вслед за перемещением разделителя. А панель 2 кроме изменения размера, должна ещё и смещаться вслед за разделителем — ведь её начальные координаты привязаны к нему. Соответственно, её размер изменяется так, чтобы она всегда оставалась внутри своего контейнера при смещении за разделителем.


Обработчик события Курсор в пределах активной области, кнопки мышки не нажаты:

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CSplitContainer::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Получаем указатель на разделитель
   CSplitter *splitter=this.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- Если разделитель не отображается
   if(!splitter.Displayed())
     {
      //--- Включаем отображение разделителя, показываем и перерисовываем его
      splitter.SetDisplayed(true);
      splitter.Show();
      splitter.Redraw(true);
     }
  }
//+------------------------------------------------------------------+

Логика метода расписана в комментариях к коду. Когда мы наводим указатель мышки на область управления, генерируется событие, которое отправляется в обработчик событий мышки объекта-формы класса CForm. Внутри обработчика идёт перенаправление на обработку каждого события в свой виртуальный метод. В объекте-форме все эти методы ничего не делают — их нужно переопределять в наследуемых классах. В данном классе элемента управления SplitContainer такой обработчик получает указатель на объект-разделитель и, если он не отображается (сброшен флаг его отображения), то для него устанавливается флаг отображения, сам объект отображается и перерисовывается.


Немного доработаем класс объекта панели элемента управления SplitContainer.

В файле класса \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\SplitContainerPanel.mqh, в публичной секции напишем методы для установки флагов свёрнутости панели и объявим метод для отображения панели и обработчик события  "Курсор в пределах активной области, кнопки мышки не нажаты":

//+------------------------------------------------------------------+
//| Класс объекта SplitContainerPanel                                |
//| элемента управления WForms SplitContainer                        |
//+------------------------------------------------------------------+
class CSplitContainerPanel : public CContainer
  {
private:
//--- Создаёт новый графический объект
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CSplitContainerPanel(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const long chart_id,
                                          const int subwindow,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h);
public:
//--- (1) Устанавливает, (2) возвращает флаг свёрнутости панели
   void              SetCollapsed(const bool flag)       { this.SetDisplayed(!flag);   }
   bool              Collapsed(void)               const { return !this.Displayed();   }
//--- Отображает панель
   virtual void      Show(void);
//--- Рисует рамку панели
   virtual void      DrawFrame(void);
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Очищает элемент заливкой градиентом
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Обработчик события  Курсор в пределах активной области, кнопки мышки не нажаты
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Конструктор
                     CSplitContainerPanel(const long chart_id,
                                          const int subwindow,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h);
  };
//+------------------------------------------------------------------+


Методы SetCollapsed() и Collapsed() —  это противоположность методам SetDisplayed() и Displayed(). Поэтому, они и вызываются внутри объявленных методов, но передаваемый в метод флаг или возвращаемый из метода инвертируются.


В методе, создающем новый графический объект, добавим создание объекта-разделителя:

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CSplitContainerPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                                     const int obj_num,
                                                     const string descript,
                                                     const int x,
                                                     const int y,
                                                     const int w,
                                                     const int h,
                                                     const color colour,
                                                     const uchar opacity,
                                                     const bool movable,
                                                     const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT                 : element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity); break;
      case GRAPH_ELEMENT_TYPE_FORM                    : element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);              break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            : element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             : element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                : element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                : element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);             break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             : element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          : element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               : element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);            break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             : element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);           break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        : element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     : element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      : element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           : element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);         break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            : element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          : element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         : element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);       break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      : element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);     break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    : element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    : element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);   break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   : element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);  break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX : element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX : element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h); break;
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      : element=new CSplitContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);    break;
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER             : element=new CSplitter(this.ChartID(),this.SubWindow(),descript,x,y,w,h);          break;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

Понятно, что все вспомогательные объекты сами по себе отдельно не представляют никакой ценности, но все объекты-контейнеры всё же должны уметь создавать все возможные объекты внутри себя. Поэтому мы и прописываем создание в объектах-контейнерах все имеющиеся и новые объекты независимо от того, к какой категории объектов библиотеки они относятся.

Метод, отображающий панель:

//+------------------------------------------------------------------+
//| Отображает панель                                                |
//+------------------------------------------------------------------+
void CSplitContainerPanel::Show(void)
  {
//--- Если панель свёрнута - уходим
   if(this.Collapsed())
      return;
//--- Отображаем панель и все прикреплённые к ней объекты
   CForm::Show();
  }
//+------------------------------------------------------------------+

Здесь сначала проверяем флаг свёрнутости панели, и если панель находится в свёрнутом состоянии, то отображать нечего — уходим. Иначе — отображаем панель при помощи метода родительского класса объекта-формы.


Обработчик события "Курсор в пределах активной области, кнопки мышки не нажаты":

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CSplitContainerPanel::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Получаем указатель на базовый объект
   CSplitContainer *base=this.GetBase();
   if(base==NULL)
      return;
//--- Из базового объекта получаем указатель на объект-разделитель
   CSplitter *splitter=base.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- Если разделитель отображается
   if(splitter.Displayed())
     {
      //--- Выключаем отображение разделителя и скрываем его
      splitter.SetDisplayed(false);
      splitter.Hide();
     }
  }
//+------------------------------------------------------------------+

Логика метода прокомментирована в коде. Вкратце: когда мы уводим курсор с области управления элемента управления SplitContainer, то курсор сразу же попадает на область первой или второй панели этого элемента управления. Т.е. мы не можем определить в объекте класса CSplitContainer, что курсор вышел за область управления — курсор сразу же попадает на прикреплённый к контейнеру объект-панель. А вот на ней-то и срабатывает событие, что курсор находится над формой или над её активной областью. Поэтому в обработчике события мышки панели нужно получить указатель на разделитель из базового объекта и скрыть полученный разделитель, что здесь и выполняется.


В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Container.mqh класса объекта-контейнера, в его методе, устанавливающем параметры присоединённому объекту, добавим блок кода для установки параметров вновь созданного объекта-разделителя:

//+------------------------------------------------------------------+
//| Устанавливает параметры присоединённому объекту                  |
//+------------------------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetObject());
//--- Устанавливаем объекту цвет текста как у базового контейнера
   obj.SetForeColor(this.ForeColor(),true);
//--- Если созданный объект не является контейнером - устанавливаем для него такую же группу, как у его базового объекта
   if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)
      obj.SetGroup(this.Group());
//--- В зависимости от типа объекта
   switch(obj.TypeGraphElement())
     {
      //--- Для WinForms-объектов "Контейнер", "Панель", "GroupBox"
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            :
      case GRAPH_ELEMENT_TYPE_WF_PANEL                :
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             :
        obj.SetBorderColor(obj.BackgroundColor(),true);
        break;
      //--- Для WinForms-объектов "Label", "CheckBox", "RadioButton"
      case GRAPH_ELEMENT_TYPE_WF_LABEL                :
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             :
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          :
        obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBackgroundColor(CLR_CANV_NULL,true);
        obj.SetOpacity(0,false);
        break;
      //--- Для WinForms-объекта "Button", "TabHeader", TabField, "ListBoxItem"
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               :
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           :
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            :
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        :
        obj.SetForeColor(this.ForeColor(),true);
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(obj.ForeColor(),true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      //--- Для WinForms-объекта "ListBox", "CheckedListBox", "ButtonListBox"
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             :
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     :
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      :
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_STD_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        break;
      //--- Для WinForms-объекта "TabControl"
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          :
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY);
        break;
      //--- Для WinForms-объекта "SplitContainer"
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER      :
        obj.SetBackgroundColor(colour==clrNONE ? CLR_CANV_NULL : colour,true);
        obj.SetBorderColor(CLR_CANV_NULL,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(0);
        break;
      //--- Для WinForms-объекта "SplitContainerPanel"
      case GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL:
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR : colour,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        break;
      //--- Для WinForms-объекта "Splitter"
      case GRAPH_ELEMENT_TYPE_WF_SPLITTER             :
        obj.SetBackgroundColor(colour==clrNONE ? CLR_CANV_NULL : colour,true);
        obj.SetBorderColor(CLR_CANV_NULL,true);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(0);
        obj.SetDisplayed(false);
        obj.Hide();
        break;
      //--- Для WinForms-объекта"ArrowButton"
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      default:
        break;
     }
   obj.Crop();
  }
//+------------------------------------------------------------------+

Для вновь созданного объекта-разделителя устанавливаем прозрачный цвет фона (если в метод передан цвет clrNONE), прозрачный цвет рамки, устанавливаем полную прозрачность объекта, флаг неотображения и скрываем созданный объект — изначально объект-разделитель не должен быть видимым.


В файле \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh класса-коллекции графических элементов, в его обработчик событий добавим блок кода для обработки перемещения объекта-разделителя:

      //--- Если событие перемещения мышки
      if(id==CHARTEVENT_MOUSE_MOVE)
        {
         //--- Если курсор над формой
         if(form!=NULL)
           {
            //--- Если стоит флаг перемещения
            if(move)
              {
               //--- рассчитываем смещение курсора относительно начала координат формы
               int x=this.m_mouse.CoordX()-form.OffsetX();
               int y=this.m_mouse.CoordY()-form.OffsetY();
               //--- получаем ширину и высоту графика, на котором находится форма
               int chart_width=(int)::ChartGetInteger(form.ChartID(),CHART_WIDTH_IN_PIXELS,form.SubWindow());
               int chart_height=(int)::ChartGetInteger(form.ChartID(),CHART_HEIGHT_IN_PIXELS,form.SubWindow());
               //--- Если форма не в составе расширенного стандартного графического объекта
               if(form_index==WRONG_VALUE)
                 {
                  //--- Если форма - объект-разделитель
                  if(form.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_SPLITTER)
                    {
                     //--- получаем его базовый объект
                     CWinFormBase *base=form.GetBase();
                     if(base==NULL)
                        return;
                     //--- и отправляем событие "Перемещение разделителя" в обработчик событий базового объекта
                     const long lp=x;
                     const double dp=y;
                     base.OnChartEvent(WF_CONTROL_EVENT_SPLITTER_MOVE,lp,dp,sparam);
                    }
                  //--- Корректируем рассчитанные координаты для формы в случае выхода формы за пределы графика
                  if(x<0) x=0;
                  if(x>chart_width-form.Width()) x=chart_width-form.Width();
                  if(y<0) y=0;
                  if(y>chart_height-form.Height()) y=chart_height-form.Height();
                  //--- Если на графике нет панели торговли в один клик
                  if(!::ChartGetInteger(form.ChartID(),CHART_SHOW_ONE_CLICK))
                    {
                     //--- рассчитаем координаты формы так, чтобы при её перемещении на кнопку включения панели торговли в один клик, форма под неё не попадала

В методе, внутри блока обработки перемещения графического объекта проверяем тип перемещаемого объекта, и если это объект-разделитель, то вызываем его обработчик событий, отправляя в него событие WF_CONTROL_EVENT_SPLITTER_MOVE. Внутри же обработчика события этого графического элемента это событие обрабатывается так, как было рассмотрено нами выше.

На сегодня это все изменения и доработки библиотеки.

Протестируем что у нас вышло.


Тестирование

Для теста возьмём советник из прошлой статьи и сохраним его в новой папке \MQL5\Experts\TestDoEasy\Part121\ под новым именем TestDoEasy121.mq5.

Всё, что требуется поменять в советнике — это координаты и размеры текстовых меток на панелях элемента управления SplitContainer:

               //--- На каждой из панелей элемента ...
               for(int j=0;j<2;j++)
                 {
                  CSplitContainerPanel *panel=split_container.GetPanel(j);
                  if(panel==NULL)
                     continue;
                  //--- ... создадим текстовую метку с названием панели
                  if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,3,3,panel.Width()-6,panel.Height()-6,clrDodgerBlue,255,true,false))
                    {
                     CLabel *label=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_LABEL,0);
                     if(label==NULL)
                        continue;
                     label.SetTextAlign(ANCHOR_CENTER);
                     label.SetText(TextByLanguage("Панель","Panel")+string(j+1));
                    }
                 }

Почему так? Просто, если размеры текстовых меток будут совпадать с размерами панелей, на которых они созданы, то при уводе курсора с области управления, курсор не попадёт на область панели, а попадёт сразу на область текстовой метки. Таким образом объект-разделитель не получится скрыть. Это недоработка, и она требует решения. Это мы решим, но по мере развития элемента управления SplitContainer.

Скомпилируем советник и запустим его на графике:


В принципе, если не обращать внимания на явные задержки с перерисовкой, то работает пока удовлетворительно. К сожалению, на моём маломощном старом ноутбуке, я не могу проверить откуда такие фризы. То ли это перегруженный процессами ноутбук не может в полной мере плавно отображать изменения координат и размеров панелей, то ли где-то в коде нужно будет далее оптимизировать. Но, как я заметил, такие фризы далеко не всегда у меня проявляются. Но в любом случае код библиотеки будет подвергаться оптимизации после окончания её разработки. Что ещё заметил: не всегда надёжно срабатывает отображение/скрытие объекта-разделителя. Это тоже решим по мере разработки элемента управления.


Что дальше

В следующей статье продолжим разработку элемента управления SplitContainer и начнём делать функционал для изменения параметров уже созданного элемента управления.

Ниже прикреплены все файлы текущей версии библиотеки, файлы тестового советника и индикатора контроля событий графиков для MQL5. Их можно скачать и протестировать всё самостоятельно. При возникновении вопросов, замечаний и пожеланий вы можете озвучить их в комментариях к статье.

К содержанию

*Статьи этой серии:

 
DoEasy. Элементы управления (Часть 13): Оптимизация взаимодействия WinForms-объектов с мышкой, начало разработки WinForms-объекта TabControl
DoEasy. Элементы управления (Часть 14): Новый алгоритм именования графических элементов. Продолжаем работу над WinForms-объектом TabControl
DoEasy. Элементы управления (Часть 15): WinForms-объект TabControl — несколько рядов заголовков вкладок, методы работы с вкладками 
DoEasy. Элементы управления (Часть 16): WinForms-объект TabControl — несколько рядов заголовков вкладок, режим растягивания заголовков под размеры контейнера
DoEasy. Элементы управления (Часть 17): Отсечение невидимых участков объектов, вспомогательные WinForms-объекты кнопки со стрелками
DoEasy. Элементы управления (Часть 18): Готовим функционал для прокрутки вкладок в TabControl
DoEasy. Элементы управления (Часть 19): Прокрутка вкладок в элементе TabControl, события WinForms-объектов
DoEasy. Элементы управления (Часть 20): WinForms-объект SplitContainer