DoEasy. Элементы управления (Часть 13): Оптимизация взаимодействия WinForms-объектов с мышкой, начало разработки WinForms-объекта TabControl

Artyom Trishkin | 29 июля, 2022

Содержание


Концепция

Графические объекты библиотеки для создания элементов управления GUI на данный момент у нас имеют недостаток: при наведении курсора на некоторые объекты они меняют свой внешний вид, но при уводе курсора с области объекта, он не всегда восстанавливает своё изначальное состояние. Это происходит, если два объекта находятся близко друг к другу и, как ни странно, от того, в какую сторону с объекта уводится курсор. Если снизу-вверх или слева-направо, то объекты правильно реагируют на воздействие на них курсора. Если же вести курсор в обратных направлениях, то графические объекты, изменив свой внешний вид при наведении на них курсора, не восстанавливают своё первоначальное состояние при его уводе с области объекта. Но стоит расположить объекты на более большой дистанции друг от друга, как они начинают себя вести правильно.

Такая "неисправность" мешала нам создавать некоторые объекты — приходилось располагать их составляющие на значительном расстоянии друг от друга, чем составляющие аналогичных объектов из набора элементов управления MS Visual Studio. Анализируя такое поведение, пришёл к выводу, что для того, чтобы объект изменил свой внешний вид на изначальный (с условием, что курсор движется в направлении, вызывающем ошибки), он должен прежде, чем войти в область рядом расположенного объекта, попасть в область формы, на которой располагаются эти графические объекты, что заставляет их располагать на расстоянии друг от друга  — не менее, чем на 4 пикселя.

Недоработка, вызывающая такое поведение графических объектов, найдена, и сегодня мы исправим эту ошибку, и заодно оптимизируем переключение цвета графических объектов при их взаимодействии с мышкой. Кроме того, начнём разрабатывать графический объект TabControl, представляющий из себя набор вкладок, на которых можно размещать различные WinForms-объекты. К сожалению, при разработке этого элемента управления всплыла не менее важная ошибка планирования структуры библиотеки, а именно — логика создания имён графических объектов. У нас имя графического объекта на данный момент состоит из имени программы с добавлением к нему наименования панели и окончаний "_Elm00", "_Elm01", "_Elm02", и т.д.

Например, если программа называется "Program", и на графике созданы три пустые объекта-панели, то имена этих трёх панелей будут такими: "Program_WFPanel1", "Program_WFPanel2" и "Program_WFPanel3". При этом имена объектам-панелям задаются пользователем библиотеки, создающем из своей программы имя создаваемой панели — WFPanel1, WFPanel2, WFPanel3 в данном случае. Если к первой панели присоединить ещё один объект, то библиотека автоматически создаст для него имя, и оно будет таким: Program_WFPanel1_Elm00.

Если к присоединённому элементу — внутри него — создать ещё один присоединённый элемент, то имя опять удлинится, и будет таким: Program_WFPanel1_Elm00_Elm00. Если на первом присоединённом элементе создать ещё один, то его имя будет таким: WFPanel1_Elm00_Elm01. Ещё один, и имя будет таким: WFPanel1_Elm00_Elm02, и т.д. Соответственно, если на каждом присоединённом объекте создавать новые присоединённые объекты, то имя будет удлиняться, так как в нём прописывается вся иерархия всех присоединённых объектов. Соответственно, это не верно, так как имя графического ресурса не должно превышать 63 символа. А так как в классе Стандартной библиотеки CCanvas при создании графического элемента создаётся графический ресурс и его имя состоит из того, которое указали мы сами (сгенерировала библиотека) плюс значение идентификатора графика, плюс количество миллисекунд, прошедших с момента старта системы, плюс псевдослучайное целое число в диапазоне от 0 до 32767 (в методе Create):

m_rcname="::"+name+(string)ChartID()+(string)(GetTickCount()+MathRand());

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

Поэтому, принятая нами ранее концепция построения имён графических элементов библиотеки стала неправильной. И с этим я уже столкнулся при разработке графического элемента TabControl — к его вкладкам уже невозможно присоединить любые другие графические элементы — просто имя графического ресурса становится слишком длинным, и метод Create() класса CCanvas возвращает false.

Исходя из вышеперечисленного, поступим так: сегодня мы подготовим необходимую базу для создания WinForm-объекта TabControl, создадим заготовки классов для его полноценного создания, но далее их разрабатывать сегодня не будем, а создадим макет этого элемента управления из "подручных средств", на котором проверим требуемый для этого объекта будущий функционал и внешний вид. Затем — в следующей статье, создадим новую механику создания имён графических элементов библиотеки и начнём делать полноценный WinForms-объект TabControl, используя заготовленные сегодня для его создания классы и полученный из макета опыт.


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

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

В файле \MQL5\Include\DoEasy\Defines.mqh напишем такие макроподстановки:

#define CLR_DEF_CONTROL_STD_BACK_COLOR_ON (C'0xC9,0xDE,0xD0')     // Цвет фона стандартных элементов управления в состоянии "Включено"
#define CLR_DEF_CONTROL_STD_BACK_DOWN_ON (C'0xA6,0xC8,0xB0')      // Цвет фона стандартных элементов управления при нажатии мышки на элемент управления в состоянии "Включено"
#define CLR_DEF_CONTROL_STD_BACK_OVER_ON (C'0xB8,0xD3,0xC0')      // Цвет фона стандартных элементов управления при наведении мышки на элемент управления в состоянии "Включено"

#define CLR_DEF_CONTROL_TAB_BACK_COLOR (CLR_CANV_NULL)            // Цвет фона элементов управления TabControl
#define CLR_DEF_CONTROL_TAB_MOUSE_DOWN (CLR_CANV_NULL)            // Цвет фона элементов управления TabControl при нажатии мышки на элемент управления
#define CLR_DEF_CONTROL_TAB_MOUSE_OVER (CLR_CANV_NULL)            // Цвет фона элементов управления TabControl при наведении мышки на элемент управления
#define CLR_DEF_CONTROL_TAB_OPACITY    (0)                        // Непрозрачность цвета фона элементов управления TabControl

#define CLR_DEF_CONTROL_TAB_BACK_COLOR_ON (CLR_CANV_NULL)         // Цвет фона элементов управления TabControl в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_BACK_DOWN_ON (CLR_CANV_NULL)          // Цвет фона элементов управления TabControl при нажатии мышки на элемент управления в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_BACK_OVER_ON (CLR_CANV_NULL)          // Цвет фона элементов управления TabControl при наведении мышки на элемент управления в состоянии "Включено"

#define CLR_DEF_CONTROL_TAB_BORDER_COLOR (CLR_CANV_NULL)          // Цвет рамки элементов управления TabControl
#define CLR_DEF_CONTROL_TAB_BORDER_MOUSE_DOWN (CLR_CANV_NULL)     // Цвет рамки элементов управления TabControl при нажатии мышки на элемент управления
#define CLR_DEF_CONTROL_TAB_BORDER_MOUSE_OVER (CLR_CANV_NULL)     // Цвет рамки элементов управления TabControl при наведении мышки на элемент управления

#define CLR_DEF_CONTROL_TAB_BORDER_COLOR_ON (CLR_CANV_NULL)       // Цвет рамки элементов управления TabControl в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_BORDER_DOWN_ON (CLR_CANV_NULL)        // Цвет рамки элементов управления TabControl при нажатии мышки на элемент управления в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_BORDER_OVER_ON (CLR_CANV_NULL)        // Цвет рамки элементов управления TabControl при наведении мышки на элемент управления в состоянии "Включено"

#define CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR (C'0xFF,0xFF,0xFF')   // Цвет фона элементов управления TabPage
#define CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN (C'0xFF,0xFF,0xFF')   // Цвет фона элементов управления TabPage при нажатии мышки на элемент управления
#define CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER (C'0xFF,0xFF,0xFF')   // Цвет фона элементов управления TabPage при наведении мышки на элемент управления
#define CLR_DEF_CONTROL_TAB_PAGE_OPACITY    (255)                 // Непрозрачность цвета фона элементов управления TabPage

#define CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR_ON (C'0xFF,0xFF,0xFF')// Цвет фона элементов управления TabPage в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_PAGE_BACK_DOWN_ON (C'0xFF,0xFF,0xFF') // Цвет фона элементов управления TabPage при нажатии мышки на элемент управления в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_PAGE_BACK_OVER_ON (C'0xFF,0xFF,0xFF') // Цвет фона элементов управления TabPage при наведении мышки на элемент управления в состоянии "Включено"

#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR (C'0xDD,0xDD,0xDD') // Цвет рамки элементов управления TabPage
#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN (C'0xDD,0xDD,0xDD') // рамки фона элементов управления TabPage при нажатии мышки на элемент управления
#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER (C'0xDD,0xDD,0xDD') // рамки фона элементов управления TabPage при наведении мышки на элемент управления

#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR_ON (C'0xDD,0xDD,0xDD')// Цвет рамки элементов управления TabPage в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_DOWN_ON (C'0xDD,0xDD,0xDD') // Цвет рамки элементов управления TabPage при нажатии мышки на элемент управления в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_PAGE_BORDER_OVER_ON (C'0xDD,0xDD,0xDD') // Цвет рамки элементов управления TabPage при наведении мышки на элемент управления в состоянии "Включено"

#define CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR (C'0xF0,0xF0,0xF0')   // Цвет фона заголовка элементов управления TabPage
#define CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN (C'0xF0,0xF0,0xF0')   // Цвет фона заголовка элементов управления TabPage при нажатии мышки на элемент управления
#define CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER (C'0xF0,0xF0,0xF0')   // Цвет фона заголовка элементов управления TabPage при наведении мышки на элемент управления
#define CLR_DEF_CONTROL_TAB_HEAD_OPACITY    (255)                 // Непрозрачность цвета фона заголовка элементов управления TabPage

#define CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON (C'0xFF,0xFF,0xFF')// Цвет фона заголовка элементов управления TabPage в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON (C'0xFF,0xFF,0xFF') // Цвет фона заголовка элементов управления TabPage при нажатии мышки на элемент управления в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON (C'0xFF,0xFF,0xFF') // Цвет фона заголовка элементов управления TabPage при наведении мышки на элемент управления в состоянии "Включено"

#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR (C'0xD9,0xD9,0xD9')   // Цвет рамки заголовка элементов управления TabPage
#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN (C'0xD9,0xD9,0xD9') // Цвет рамки заголовка элементов управления TabPage при нажатии мышки на элемент управления
#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER (C'0xD9,0xD9,0xD9') // Цвет рамки заголовка элементов управления TabPage при наведении мышки на элемент управления

#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR_ON (C'0xDD,0xDD,0xDD')// Цвет рамки заголовка элементов управления TabPage в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_DOWN_ON (C'0xDD,0xDD,0xDD') // Цвет рамки заголовка элементов управления TabPage при нажатии мышки на элемент управления в состоянии "Включено"
#define CLR_DEF_CONTROL_TAB_HEAD_BORDER_OVER_ON (C'0xDD,0xDD,0xDD') // Цвет рамки заголовка элементов управления TabPage при наведении мышки на элемент управления в состоянии "Включено"

#define DEF_CONTROL_LIST_MARGIN_X      (1)                        // Зазор между столбцами в элементах управления ListBox
#define DEF_CONTROL_LIST_MARGIN_Y      (0)                        // Зазор между строками в элементах управления ListBox

#define DEF_FONT                       ("Calibri")                // Шрифт по умолчанию
#define DEF_FONT_SIZE                  (8)                        // Размер шрифта по умолчанию
#define DEF_CHECK_SIZE                 (12)                       // Размер флажка проверки по умолчанию
#define OUTER_AREA_SIZE                (16)                       // Размер одной стороны внешней области вокруг рабочего пространства формы
#define DEF_FRAME_WIDTH_SIZE           (3)                        // Ширина рамки формы/панели/окна по умолчанию
//--- Параметры графических объектов

Здесь у нас добавлены ещё две макроподстановки, определяющие зазор между строками в элементах управления ListBox. Ранее мы вынуждены были использовать 4 пикселя по высоте и 6 пикселей по ширине. Теперь же — после исправления ошибки изменения внешнего вида элементов при взаимодействии с мышкой, мы можем указать минимальные зазоры между строками по высоте и рядами колонок строк по ширине, что здесь и задаётся.


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

//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
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_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
   GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,                  // Windows Forms TabHeader
   GRAPH_ELEMENT_TYPE_WF_TAB_PAGE,                    // Windows Forms TabPage
   GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,                 // Windows Forms TabControl
  };
//+------------------------------------------------------------------+

Эти новые типы графических элементов нам потребуются для указания типа либо самого WinForms-объекта TabControl, либо его составляющих — заголовка вкладки (TabHeader) или вкладки (TabPage). То есть объект будет состоять из набора вкладок, а вкладки будут состоять из панели, где будут размещаться прикрепляемые объекты, и заголовка панели, щелчок по которой будет делать активной соответствующую вкладку.

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

//+------------------------------------------------------------------+
//| Состояния флажка элемента управления                             |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_CHEK_STATE
  {
   CANV_ELEMENT_CHEK_STATE_UNCHECKED,                 // Не установлен
   CANV_ELEMENT_CHEK_STATE_CHECKED,                   // Установлен
   CANV_ELEMENT_CHEK_STATE_INDETERMINATE,             // Неопределённый
  };
//+------------------------------------------------------------------+
//| Местоположение объекта внутри элемента управления                |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_ALIGNMENT
  {
   CANV_ELEMENT_ALIGNMENT_TOP,                        // Сверху
   CANV_ELEMENT_ALIGNMENT_BOTTOM,                     // Снизу
   CANV_ELEMENT_ALIGNMENT_LEFT,                       // Слева
   CANV_ELEMENT_ALIGNMENT_RIGHT,                      // Справа
  };
//+------------------------------------------------------------------+
//| Целочисленные свойства графического элемента на канвасе          |
//+------------------------------------------------------------------+


При создании наименований констант ранее была допущена небольшая неточность: у нас кнопка может быть кнопкой-переключателем (toggle-button), но состояние кнопки "нажата/отжата" — это не toggle-состояние. Нажатое состояние в наименованиях констант перечислений лучше переименовать в StateOn.

Поэтому все перечисления, в которых упоминается цвет для нажатого состояния и который ранее был назван как "COLOR_TOGGLE", теперь переименованы в "COLOR_STATE_ON". Все исправления наименований констант перечислений уже сделаны в библиотеке, и здесь мы о них лишь упоминаем в качестве информации.
Приведу пример переименования некоторых констант в обсуждаемом файле в перечислении целочисленных свойств графического элемента на канвасе:

   CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN,           // Цвет текста элемента управления по умолчанию при нажатии мышки на элемент управления
   CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER,           // Цвет текста элемента управления по умолчанию при наведении мышки на элемент управления
   CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON,             // Цвет текста элемента управления в состоянии "включено"
   CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN,  // Цвет текста элемента управления по умолчанию в состоянии "включено" при нажатии мышки на элемент управления
   CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER,  // Цвет текста элемента управления по умолчанию в состоянии "включено" при наведении мышки на элемент управления
   CANV_ELEMENT_PROP_BACKGROUND_COLOR,                // Цвет фона элемента управления
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY,        // Непрозрачность цвета фона элемента управления
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,     // Цвет фона элемента управления при нажатии мышки на элемент управления
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,     // Цвет фона элемента управления при наведении мышки на элемент управления
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON,       // Цвет фона элемента управления в состоянии "включено"
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON_MOUSE_DOWN,// Цвет фона элемента управления в состоянии "включено" при нажатии мышки на элемент управления
   CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON_MOUSE_OVER,// Цвет фона элемента управления в состоянии "включено" при наведении мышки на элемент управления
   CANV_ELEMENT_PROP_BOLD_TYPE,                       // Тип толщины шрифта
   CANV_ELEMENT_PROP_BORDER_STYLE,                    // Стиль рамки элемента управления


В самом конце этого же перечисления допишем три новых целочисленных свойства и увеличим количество целочисленных свойств объекта с 85 до 88:

   CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,           // Горизонтальное отображение столбцов в элементе управления ListBox
   CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,           // Ширина каждого столбца элемента управления ListBox
   CANV_ELEMENT_PROP_TAB_MULTILINE,                   // Несколько рядов вкладок в элементе управления TabControl
   CANV_ELEMENT_PROP_TAB_ALIGNMENT,                   // Местоположение вкладок внутри элемента управления
   CANV_ELEMENT_PROP_ALIGNMENT,                       // Местоположение объекта внутри элемента управления
   
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (88)          // Общее количество целочисленных свойств
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Количество неиспользуемых в сортировке целочисленных свойств
//+------------------------------------------------------------------+


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

   SORT_BY_CANV_ELEMENT_LIST_BOX_MULTI_COLUMN,        // Сортировать по флагу горизонтального отображения столбцов в элементе управления ListBox
   SORT_BY_CANV_ELEMENT_LIST_BOX_COLUMN_WIDTH,        // Сортировать по ширине каждого столбца элемента управления ListBox
   SORT_BY_CANV_ELEMENT_TAB_MULTILINE,                // Сортировать по флагу нескольких рядов вкладок в элементе управления TabControl
   SORT_BY_CANV_ELEMENT_TAB_ALIGNMENT,                // Сортировать по местоположению вкладок внутри элемента управления
   SORT_BY_CANV_ELEMENT_ALIGNMENT,                    // Сортировать по местоположению объекта внутри элемента управления
//--- Сортировка по вещественным свойствам

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

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


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

   MSG_LIB_TEXT_BUTTON_STATE_PRESSED,                 // Нажата
   MSG_LIB_TEXT_BUTTON_STATE_DEPRESSED,               // Отжата
   
   MSG_LIB_TEXT_TOP,                                  // Сверху
   MSG_LIB_TEXT_BOTTOM,                               // Снизу
   MSG_LIB_TEXT_LEFT,                                 // Слева
   MSG_LIB_TEXT_RIGHT,                                // Справа
   
   MSG_LIB_TEXT_CORNER_LEFT_UPPER,                    // Центр координат в левом верхнем углу графика
   MSG_LIB_TEXT_CORNER_LEFT_LOWER,                    // Центр координат в левом нижнем углу графика
   MSG_LIB_TEXT_CORNER_RIGHT_LOWER,                   // Центр координат в правом нижнем углу графика
   MSG_LIB_TEXT_CORNER_RIGHT_UPPER,                   // Центр координат в правом верхнем углу графика

...

   MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,        // Элемент управления CheckedListBox
   MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,         // Элемент управления ButtonListBox
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,              // Заголовок вкладки
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE,                // Элемент управления TabPage
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,             // Элемент управления TabControl
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Графический объект принадлежит программе
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Графический объект не принадлежит программе

..

   MSG_CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN,       // Горизонтальное отображение столбцов в элементе управления ListBox
   MSG_CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH,       // Ширина каждого столбца элемента управления ListBox
   MSG_CANV_ELEMENT_PROP_TAB_MULTILINE,               // Несколько рядов вкладок в элементе управления
   MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT,               // Местоположение вкладок внутри элемента управления
   MSG_CANV_ELEMENT_PROP_ALIGNMENT,                   // Местоположение объекта внутри элемента управления
//--- Вещественные свойства графических элементов

//--- Строковые свойства графических элементов
   MSG_CANV_ELEMENT_PROP_NAME_OBJ,                    // Имя объекта-графического элемента
   MSG_CANV_ELEMENT_PROP_NAME_RES,                    // Имя графического ресурса
   MSG_CANV_ELEMENT_PROP_TEXT,                        // Текст графического элемента

  };
//+------------------------------------------------------------------+

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

   {"Нажата","Pressed"},
   {"Отжата","Depressed"},
   
   {"Сверху","Top"},
   {"Снизу","Bottom"},
   {"Слева","Left"},
   {"Справа","Right"},
   
   {"Центр координат в левом верхнем углу графика","Center of coordinates is in the upper left corner of the chart"},
   {"Центр координат в левом нижнем углу графика","Center of coordinates is in the lower left corner of the chart"},
   {"Центр координат в правом нижнем углу графика","Center of coordinates is in the lower right corner of the chart"},
   {"Центр координат в правом верхнем углу графика","Center of coordinates is in the upper right corner of the chart"},

...

   {"Элемент управления \"CheckedListBox\"","Control element \"CheckedListBox\""},
   {"Элемент управления \"ButtonListBox\"","Control element \"ButtonListBox\""},
   {"Заголовок вкладки","Tab header"},
   {"Элемент управления \"TabPage\"","Control element \"TabPage\""},
   {"Элемент управления \"TabControl\"","Control element \"TabControl\""},
   
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},

..

   {"Горизонтальное отображение столбцов в элементе управления ListBox","Display columns horizontally in a ListBox control"},
   {"Ширина каждого столбца элемента управления ListBox","The width of each column of the ListBox control"},
   {"Несколько рядов вкладок в элементе управления","Multiple rows of tabs in a control"},
   {"Местоположение вкладок внутри элемента управления","Location of tabs inside the control"},
   {"Местоположение объекта внутри элемента управления","Location of the object inside the control"},
   
//--- Строковые свойства графических элементов
   {"Имя объекта-графического элемента","The name of the graphic element object"},
   {"Имя графического ресурса","Image resource name"},
   {"Текст графического элемента","Text of the graphic element"},

  };
//+---------------------------------------------------------------------+


Новые типы графических элементов мы добавили, их описание тоже, и теперь мы можем обратиться к индексам текстовых сообщений в методе, возвращающем описание типа графического элемента, класса базового графического объекта в файле \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_HEADER          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)         :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_PAGE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_PAGE)           :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)        :
      //--- Стандартные элементы управления
      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_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)    :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+


Заголовок вкладки элемента управления TabControl может располагаться в четырёх сторонах графического объекта. Также, в последующем, у нас могут быть и другие объекты, для которых необходимо будет указать их местоположение внутри своего контейнера. Поэтому сделаем общедоступную функцию, возвращающую описание стороны расположения графического элемента в файле \MQL5\Include\DoEasy\Services\DELib.mqh:

//+------------------------------------------------------------------+
//| Возвращает описание местоположения                               |
//| объекта внутри элемента управления                               |
//+------------------------------------------------------------------+
string AlignmentDescription(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)
  {
   switch(alignment)
     {
      case CANV_ELEMENT_ALIGNMENT_TOP     :  return CMessage::Text(MSG_LIB_TEXT_TOP);     break;
      case CANV_ELEMENT_ALIGNMENT_BOTTOM  :  return CMessage::Text(MSG_LIB_TEXT_BOTTOM);  break;
      case CANV_ELEMENT_ALIGNMENT_LEFT    :  return CMessage::Text(MSG_LIB_TEXT_LEFT);    break;
      case CANV_ELEMENT_ALIGNMENT_RIGHT   :  return CMessage::Text(MSG_LIB_TEXT_RIGHT);   break;
      default                             :  return "Unknown"; break;
     }
  }
//+------------------------------------------------------------------+
//| Возвращает флаг отображения графического                         |
//| объекта на указанном таймфрейме графика                          |
//+------------------------------------------------------------------+

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


Теперь немного об оптимизации.

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

В файле \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh в методах установки цвета внесём озвученные доработки:

//--- Устанавливает основной цвет фона
   void              SetBackgroundColor(const color colour,const bool set_init_color)
                       {
                        if(this.BackgroundColor()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,colour);
                        color arr[1];
                        arr[0]=colour;
                        this.SaveColorsBG(arr);
                        if(set_init_color)
                           this.SetBackgroundColorInit(this.BackgroundColor());
                       }
   void              SetBackgroundColors(color &colors[],const bool set_init_colors)
                       {
                        if(::ArrayCompare(colors,this.m_array_colors_bg)==0)
                           return;
                        this.SaveColorsBG(colors);
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR,this.m_array_colors_bg[0]);
                        if(set_init_colors)
                           this.SetBackgroundColorsInit(colors);
                       }
//--- Устанавливает цвет фона при нажатии мышки на элемент управления
   void              SetBackgroundColorMouseDown(const color colour)
                       {
                        if(this.BackgroundColorMouseDown()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,colour);
                        color arr[1];
                        arr[0]=colour;
                        this.SaveColorsBGMouseDown(arr);
                       }
   void              SetBackgroundColorsMouseDown(color &colors[])
                       {
                        if(::ArrayCompare(colors,this.m_array_colors_bg_dwn)==0)
                           return;
                        this.SaveColorsBGMouseDown(colors);
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,this.m_array_colors_bg_dwn[0]);
                       }
//--- Устанавливает цвет фона при наведении мышки на элемент управления
   void              SetBackgroundColorMouseOver(const color colour)
                       {
                        if(this.BackgroundColorMouseOver()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,colour);
                        color arr[1];
                        arr[0]=colour;
                        this.SaveColorsBGMouseOver(arr);
                       }
   void              SetBackgroundColorsMouseOver(color &colors[])
                       {
                        if(::ArrayCompare(colors,this.m_array_colors_bg_ovr)==0)
                           return;
                        this.SaveColorsBGMouseOver(colors);
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_OVER,this.m_array_colors_bg_ovr[0]);
                       }
//--- Устанавливает изначальный основной цвет фона

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

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

   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.m_name);
     }
  }
//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+

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

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


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

В файле класса объекта-формы \MQL5\Include\DoEasy\Objects\Graph\Form.mqh впишем ранее оговоренные изменения в методы для работы с цветом:

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта                   |
//+------------------------------------------------------------------+
//--- (1) Устанавливает, (2) возвращает цвет рамки элемента управления
   void              SetBorderColor(const color colour,const bool set_init_color)
                       {
                        if(this.BorderColor()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR,colour);
                        if(set_init_color)
                           this.SetBorderColorInit(colour);
                       }
   color             BorderColor(void)                            const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR);             }
//--- (1) Устанавливает, (2) возвращает цвет рамки элемента управления при нажатии мышки на элемент управления
   void              SetBorderColorMouseDown(const color colour)
                       {
                        if(this.BorderColorMouseDown()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN,colour);
                       }
   color             BorderColorMouseDown(void)                   const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_DOWN);  }
//--- (1) Устанавливает, (2) возвращает цвет рамки элемента управления при наведении мышки на элемент управления
   void              SetBorderColorMouseOver(const color colour)
                       {
                        if(this.BorderColorMouseOver()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER,colour);
                       }
   color             BorderColorMouseOver(void)                   const { return (color)this.GetProperty(CANV_ELEMENT_PROP_BORDER_COLOR_MOUSE_OVER);  }


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

//+------------------------------------------------------------------+
//| Рисует рамку формы                                               |
//+------------------------------------------------------------------+
void CForm::DrawFormFrame(const int wd_top,              // Ширина верхнего сегмента рамки
                          const int wd_bottom,           // Ширина нижнего сегмента рамки
                          const int wd_left,             // Ширина левого сегмента рамки
                          const int wd_right,            // Ширина правого сегмента рамки
                          const color colour,            // Цвет рамки
                          const uchar opacity,           // Непрозрачность рамки
                          const ENUM_FRAME_STYLE style)  // Стиль рамки
  {
//--- В зависимости от переданного стиля рамки
   switch(style)
     {
      //--- рисуем объёмную (выпуклую) рамку
      case FRAME_STYLE_BEVEL  : this.DrawFrameBevel(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);   break;
      //--- рисуем объёмную (вдавленную) рамку
      case FRAME_STYLE_STAMP  : this.DrawFrameStamp(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);   break;
      //--- рисуем плоскую рамку
      case FRAME_STYLE_FLAT   : this.DrawFrameFlat(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);    break;
      //--- рисуем простую рамку
      case FRAME_STYLE_SIMPLE : this.DrawFrameSimple(0,0,this.Width(),this.Height(),wd_top,wd_bottom,wd_left,wd_right,colour,opacity);  break;
      //---FRAME_STYLE_NONE
      default                 : break;
     }
  }
//+------------------------------------------------------------------+


В обработчике последнего события мышки, добавим условие, что последним событием мышки было неопределённое состояние, а текущее состояние — не нажата кнопка за пределами формы и неопределённое состояние:

//+------------------------------------------------------------------+
//| Обработчик последнего события мышки                              |
//+------------------------------------------------------------------+
void CForm::OnMouseEventPostProcessing(void)
  {
   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);
           //this.Redraw(false);
          }
        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        :
        break;
      //---MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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

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


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

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

//+------------------------------------------------------------------+
//| Класс объекта "форма"                                            |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
protected:
   color             m_fore_color_init;                        // Первоначальный цвет текста элемента
   color             m_fore_state_on_color_init;               // Первоначальный цвет текста элемента в состоянии "ON"
private:


Доработаем, как и ранее выше, методы для оптимизации работы с цветом:

//--- (1) Устанавливает, (2) возвращает цвет текста по умолчанию всех объектов на панели
   void              SetForeColor(const color clr,const bool set_init_color)
                       {
                        if(this.ForeColor()==clr)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR,clr);
                        if(set_init_color)
                           this.SetForeColorInit(clr);
                       }
   color             ForeColor(void)                           const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR);                     }
//--- (1) Устанавливает, (2) возвращает первоначальный цвет текста по умолчанию всех объектов на панели
   void              SetForeColorInit(const color clr)               { this.m_fore_color_init=clr;                                                       }
   color             ForeColorInit(void)                       const { return (color)this.m_fore_color_init;                                             }
//--- (1) Устанавливает, (2) возвращает непрозрачность цвета текста по умолчанию всех объектов на панели
   void              SetForeColorOpacity(const uchar value)
                       {
                        if(this.ForeColorOpacity()==value)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY,value);
                       }
   uchar             ForeColorOpacity(void)                    const { return (uchar)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY);             }
//--- (1) Устанавливает, (2) возвращает цвет текста элемента при нажатии мышки на элемент управления
   void              SetForeColorMouseDown(const color clr)
                       {
                        if(this.ForeColorMouseDown()==clr)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN,clr);
                       }
   color             ForeColorMouseDown(void)                  const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_DOWN);          }
//--- (1) Устанавливает, (2) возвращает цвет текста элемента при наведении мышки на элемент управления
   void              SetForeColorMouseOver(const color clr)
                       {
                        if(this.ForeColorMouseOver()==clr)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER,clr);
                       }
   color             ForeColorMouseOver(void)                  const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER);          }


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

   color             ForeColorMouseOver(void)                  const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_MOUSE_OVER);          }
   
//--- (1) Устанавливает, (2) возвращает первоначальный цвет текста в состоянии "Включено" по умолчанию всех объектов на панели
   void              SetForeStateOnColorInit(const color clr)        { this.m_fore_state_on_color_init=clr;                                              }
   color             ForeStateOnColorInit(void)                const { return (color)this.m_fore_state_on_color_init;                                    }
//--- (1) Устанавливает, (2) возвращает основной цвет текста для состояния "включено"
   void              SetForeStateOnColor(const color colour,const bool set_init_color)
                       {
                        if(this.ForeStateOnColor()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON,colour);
                        if(set_init_color)
                           this.SetForeStateOnColorInit(colour);
                       }
   color             ForeStateOnColor(void)                    const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON);            }
   
//--- (1) Устанавливает, (2) возвращает цвет текста при нажатии мышки на элемент управления для состояния "включено"
   void              SetForeStateOnColorMouseDown(const color colour)
                       {
                        if(this.ForeStateOnColorMouseDown()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN,colour);
                       }
   color             ForeStateOnColorMouseDown(void)           const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_DOWN); }
                       
//--- (1) Устанавливает, (2) возвращает цвет текста при наведении мышки на элемент управления для состояния "включено"
   void              SetForeStateOnColorMouseOver(const color colour)
                       {
                        if(this.ForeStateOnColorMouseOver()==colour)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER,colour);
                       }
   color             ForeStateOnColorMouseOver(void)           const { return (color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_STATE_ON_MOUSE_OVER); }
   
//--- (1) Устанавливает, (2) возвращает текст элемента

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

В конструкторе класса добавим установку цвета текста объекта в состоянии "включено":

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(const long chart_id,
                           const int subwindow,
                           const string name,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(chart_id,subwindow,name,x,y,w,h)
  {
//--- Установим тип графического элемента и тип объекта библиотеки как базовый WinForms-объект
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BASE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_BASE; 
//--- Инициализируем все переменные
   this.SetText("");
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetForeStateOnColor(this.ForeColor(),true);
   this.SetForeStateOnColorMouseDown(this.ForeColor());
   this.SetForeStateOnColorMouseOver(this.ForeColor());
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(0);
   this.SetPaddingAll(0);
   this.SetBorderSizeAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoSize(false,false);
   CForm::SetCoordXInit(x);
   CForm::SetCoordYInit(y);
   CForm::SetWidthInit(w);
   CForm::SetHeightInit(h);
   this.m_shadow=false;
   this.m_gradient_v=true;
   this.m_gradient_c=false;
  }
//+------------------------------------------------------------------+

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


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

      property==CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_CHECK_FLAG_COLOR_MOUSE_OVER)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+::ColorToString((color)this.GetProperty(property),true)
         )  :
      property==CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_LIST_BOX_MULTI_COLUMN)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH  ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_LIST_BOX_COLUMN_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TAB_MULTILINE                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_MULTILINE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TAB_ALIGNMENT                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      property==CANV_ELEMENT_PROP_ALIGNMENT                    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

Здесь два первых блока должны были быть вписаны ещё в прошлой статье. Забыли — исправляемся...


В WinForms-объекте CheckBox мы до сих пор рисовали флажок проверки по жёстко заданным координатам внутри своего поля. Это не правильно, так как при изменении размеров поля, флажок проверки будет растянут или сжат по жёстко заданным координатам. Для правильного его отображения нужно использовать не координаты с отступом в пикселях от краёв поля флажка, а относительные координаты — в процентах от размера поля. Исправим это.

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh в методе, отображающем флажок проверки по указанному состоянию, сделаем расчёт относительных координат флажка проверки в состоянии "установлен" и "не определён". Рассчитывать будем в вещественных значениях, а потом приводить к целочисленным и передавать эти координаты в методы рисования примитивов класса CCanvas:

//+------------------------------------------------------------------+
//| Отображает флажок проверки по указанному состоянию               |
//+------------------------------------------------------------------+
void CCheckBox::ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state)
  {
//--- Рисуем закрашенный прямоугольник области флажка выбора
   this.DrawRectangleFill(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBackgroundColor(),this.CheckBackgroundColorOpacity());
//--- Рисуем прямоугольник границ флажка
   this.DrawRectangle(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.CheckBorderColor(),this.CheckBorderColorOpacity());
//--- Создаём массивы координат X и Y для рисования ломанной линии
   double x=(double)this.m_check_x;
   double y=(double)this.m_check_y;
   double w=(double)this.m_check_w;
   double h=(double)this.m_check_h;
//--- Координаты рассчитываем как double-значения и записываем их как целочисленные значения в массивы
   int array_x[]={int(x+w*0.2), int(x+w*0.45), int(x+w*0.85), int(x+w*0.45)};
   int array_y[]={int(y+h*0.5), int(y+h*0.6),  int(y+h*0.3),  int(y+h*0.75)};
//--- В зависимости от переданног в метод состояния флажка
   switch(state)
     {
      //--- Установленный флажок
      case CANV_ELEMENT_CHEK_STATE_CHECKED :
        //--- Рисуем внутри границ флажка сначала закрашенный многоугольник,
        //--- а поверх него - сглаженный многоугольник в виде галочки
        this.DrawPolygonFill(array_x,array_y,this.CheckFlagColor(),this.CheckFlagColorOpacity());
        this.DrawPolygonAA(array_x,array_y,this.CheckFlagColor(),this.CheckFlagColorOpacity());
        break;
      //--- Неопределённое состояние
      case CANV_ELEMENT_CHEK_STATE_INDETERMINATE :
        //--- Рисуем закрашенный прямоугольник внутри границ флажка
        this.DrawRectangleFill(int(x+w*0.3),int(y+h*0.3),int(x+w*0.7),int(y+h*0.7),this.CheckFlagColor(),this.CheckFlagColorOpacity());
        break;
      //--- Не установленный флажок
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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

Внесём доработки в класс объекта-кнопки в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Button.mqh.

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

//--- Устанавливает цвета для состояния "включено"
   void              SetStateOnColors(const color back,
                                      const color back_down,
                                      const color back_over,
                                      const color fore,
                                      const color fore_down,
                                      const color fore_over,
                                      const bool set_init_color);

За пределами тела класса, в коде реализации метода, допишем установку переданных цветов в свойства объекта:

//+------------------------------------------------------------------+
//| Устанавливает цвета для состояния "включено" toggle-элемента     |
//+------------------------------------------------------------------+
void CButton::SetStateOnColors(const color back,
                               const color back_down,
                               const color back_over,
                               const color fore,
                               const color fore_down,
                               const color fore_over,
                               const bool set_init_color)
  {
   this.SetBackgroundStateOnColor(back,set_init_color);
   this.SetBackgroundStateOnColorMouseDown(back_down);
   this.SetBackgroundStateOnColorMouseOver(back_over);
   this.SetForeStateOnColor(fore,set_init_color);
   this.SetForeStateOnColorMouseDown(fore_down);
   this.SetForeStateOnColorMouseOver(fore_over);
  }
//+------------------------------------------------------------------+


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

//--- (1) Устанавливает, (2) возвращает флаг "Переключатель" элемента управления
   void              SetToggleFlag(const bool flag)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_BUTTON_TOGGLE,flag);
                        if(this.Toggle())
                           this.SetStateOnColors
                             (
                              this.BackgroundStateOnColor(),this.BackgroundStateOnColorMouseDown(),this.BackgroundStateOnColorMouseOver(),
                              this.ForeStateOnColor(),this.ForeStateOnColorMouseDown(),this.ForeStateOnColorMouseOver(),true
                             );
                       }

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

//--- (1) Устанавливает, (2) возвращает состояние элемента управления "Переключатель"
   void              SetState(const bool flag)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_BUTTON_STATE,flag);
                        if(this.State())
                          {
                           this.SetBackgroundColor(this.BackgroundStateOnColor(),false);
                           this.SetForeColor(this.ForeStateOnColor(),false);
                           this.UnpressOtherAll();
                          }
                        else
                          {
                           this.SetBackgroundColor(this.BackgroundColorInit(),false);
                           this.SetForeColor(this.ForeColorInit(),false);
                           this.SetBorderColor(this.BorderColorInit(),false);
                          }
                       }

Все методы, в которых в их наименовании есть строка "ColorToggleON", теперь переименованы, и эта строка заменена на "StateOnColor", а все константы перечислений тоже соответственно переименованы. Для примера, метод, устанавливающий основной цвет фона для состояния "включено":

   void              SetBackgroundStateOnColor(const color colour,const bool set_init_color)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_STATE_ON,colour);
                        color arr[1];
                        arr[0]=colour;
                        this.CopyArraysColors(this.m_array_colors_bg_tgl,arr,DFUN);
                        if(set_init_color)
                           this.CopyArraysColors(this.m_array_colors_bg_tgl_init,arr,DFUN);
                       }

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CButton::CButton(const long chart_id,
                 const int subwindow,
                 const string name,
                 const int x,
                 const int y,
                 const int w,
                 const int h) : CLabel(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BUTTON);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BUTTON);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetCoordX(x);
   this.SetCoordY(y);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   this.SetBackgroundColor(CLR_DEF_CONTROL_STD_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_STD_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_STD_MOUSE_OVER);
   this.SetBackgroundStateOnColor(CLR_DEF_CONTROL_STD_BACK_COLOR_ON,true);
   this.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_STD_BACK_DOWN_ON);
   this.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_STD_BACK_OVER_ON);
   this.SetOpacity(CLR_DEF_CONTROL_STD_OPACITY);
   this.SetTextAlign(ANCHOR_CENTER);
   this.SetMarginAll(3);
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetToggleFlag(false);
   this.SetState(false);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CButton::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Если это простая кнопка - устанавливаем цвет фона и текста для состояния "Курсор над активной зоной, кнопка мышки не нажата"
   if(!this.Toggle())
     {
      this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
      this.SetForeColor(this.ForeColorMouseOver(),false);
     }
//--- Если это кнопка-переключатель - устанавливаем цвет фона и текста для этого состояния в зависимости от того нажата кнопка или нет
   else
     {
      this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false);
      this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false);
     }
//--- Устанавливаем цвет рамки для этого состояния
   this.SetBorderColor(this.BorderColorMouseOver(),false);
//--- Перерисовываем объект
   this.Redraw(false);
  }
//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| нажата кнопка мышки (любая)                                      |
//+------------------------------------------------------------------+
void CButton::MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Если это простая кнопка - устанавливаем цвет фона и текста для состояния "Курсор над активной зоной, кнопка мышки нажата"
   if(!this.Toggle())
     {
      this.SetBackgroundColor(this.BackgroundColorMouseDown(),false);
      this.SetForeColor(this.ForeColorMouseDown(),false);
     }
//--- Если это кнопка-переключатель - устанавливаем цвет фона и текста для этого состояния в зависимости от того нажата кнопка или нет
   else
     {
      this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseDown() : this.BackgroundColorMouseDown(),false);
      this.SetForeColor(this.State() ? this.ForeStateOnColorMouseDown() : this.ForeColorMouseDown(),false);
     }
//--- Устанавливаем цвет рамки для этого состояния
   this.SetBorderColor(this.BorderColorMouseDown(),false);
//--- Перерисовываем объект
   this.Redraw(false);
  }
//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| отжата кнопка мышки (левая)                                      |
//+------------------------------------------------------------------+
void CButton::MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Если кнопка мышки отпущена за пределами элемента - это отказ от взаимодействия с элементом
   if(lparam<this.CoordX() || lparam>this.RightEdge() || dparam<this.CoordY() || dparam>this.BottomEdge())
     {
      //--- Если это простая кнопка - устанавливаем изначальный цвет фона и текста
      if(!this.Toggle())
        {
         this.SetBackgroundColor(this.BackgroundColorInit(),false);
         this.SetForeColor(this.ForeColorInit(),false);
        }
      //--- Если это кнопка-переключатель - устанавливаем изначальный цвет фона и текста в зависимости от того нажата кнопка или нет
      else
        {
         this.SetBackgroundColor(!this.State() ? this.BackgroundColorInit() : this.BackgroundStateOnColorInit(),false);
         this.SetForeColor(!this.State() ? this.ForeColorInit() : this.ForeStateOnColorInit(),false);
        }
      //--- Устанавливаем изначальный цвет рамки
      this.SetBorderColor(this.BorderColorInit(),false);
      //--- Выводим тестовое сообщение в журнал
      Print(DFUN_ERR_LINE,TextByLanguage("Отмена","Cancel"));
     }
//--- Если кнопка мышки отпущена в пределах элемента - это щелчок по элементу управления
   else
     {
      //--- Если это простая кнопка - устанавливаем цвет фона и текста для состояния "Курсор мышки над активной зоной"
      if(!this.Toggle())
        {
         this.SetBackgroundColor(this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.ForeColorMouseOver(),false);
        }
      //--- Если это кнопка-переключатель -
      else
        {
         //--- если кнопка не работает в группе - устанавливаем её состояние на противоположное,
         if(!this.GroupButtonFlag())
            this.SetState(!this.State());
         //--- иначе - если кнопка ещё не нажата - устанавливаем её в нажатое состояние
         else if(!this.State())
            this.SetState(true);
         //--- устанавливаем цвет фона и текста для состояния "Курсор мышки над активной зоной" в зависимости от того нажата кнопка или нет
         this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false);
        }
      //--- Выводим тестовое сообщение в журнал
      Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group());
      //--- Устанавливаем цвет рамки для состояния "Курсор мышки над активной зоной"
      this.SetBorderColor(this.BorderColorMouseOver(),false);
     }
//--- Перерисовываем объект
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

В обработчике последнего события мышки добавим восстановление цвета текста объекта в зависимости от его состояния (включено/выключено):

//+------------------------------------------------------------------+
//| Обработчик последнего события мышки                              |
//+------------------------------------------------------------------+
void CButton::OnMouseEventPostProcessing(void)
  {
   ENUM_MOUSE_FORM_STATE state=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       :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED || this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED)
          {
           this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColor() : this.BackgroundColorInit(),false);
           this.SetForeColor(this.State() ? this.ForeStateOnColor() : this.ForeColorInit(),false);
           this.SetBorderColor(this.BorderColorInit(),false);
           this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
           this.Redraw(false);
          }
        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 :
        break;
      //---MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Устанавливает состояние кнопки как "отжато"                      |
//| для всех Button одной группы в контейнере                        |
//+------------------------------------------------------------------+
void CButton::UnpressOtherAll(void)
  {
//--- Получаем указатель на базовый объект
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Из базового объекта получаем список всех объектов с типом Button
   CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_BUTTON);
//--- Из полученного списка выбираем все объекты, кроме данного (имена выбранных объектов не равны имени этого)
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,this.Name(),NO_EQUAL);
//--- Из полученного списка выбираем только те объекты, у которых номер группы совпадает с группой этого
   list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_GROUP,this.Group(),EQUAL);
//--- Если список объектов получен,
   if(list!=NULL)
     {
      //--- в цикле по всем объектам в списке
      for(int i=0;i<list.Total();i++)
        {
         //--- получаем очередной объект,
         CButton *obj=list.At(i);
         if(obj==NULL)
            continue;
         //--- устанавливаем кнопке состояние "отжата",
         obj.SetState(false);
         //--- устанавливаем цвет фона изначальным (курсор находится на другой кнопке за пределами этой)
         obj.SetBackgroundColor(obj.BackgroundColorInit(),false);
         obj.SetForeColor(obj.ForeColorInit(),false);
         obj.SetBorderColor(obj.BorderColorInit(),false);
         //--- Перерисовываем объект для отображения изменений
         obj.Redraw(false);
        }
     }
  }
//+------------------------------------------------------------------+

Теперь кнопки смогут менять цвет текста, выводимого на них в зависимости от состояния кнопки (нажата/отжата) при взаимодействии с курсором мышки.

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

//+------------------------------------------------------------------+
//| Возвращает координаты очередного размещаемого в списке объекта   |
//+------------------------------------------------------------------+
void CElementsListBox::GetCoordsObj(CWinFormBase *obj,int &x,int &y)
  {
//--- Сохраним в переменных переданные в метод координаты
   int coord_x=x;
   int coord_y=y;
//--- Если не стоит флаг использования нескольких столбцов -
   if(!this.MultiColumn())
     {
      //--- устанавливаем координату X такую же, как переданная в метод,
      //--- координату Y устанавливаем для первого объекта в списке равную переданной в метод,
      //--- для остальных - ниже на 4 пикселя нижнего края предыдущего, вышестоящего объекта.
      //--- После установки координат в переменные, уходим из метода
      x=coord_x;
      y=(obj==NULL ? coord_y : obj.BottomEdgeRelative()+DEF_CONTROL_LIST_MARGIN_Y);
      return;
     }
//--- Если возможно использование нескольких столбцов
//--- Если это первый объект в списке - 
   if(obj==NULL)
     {
      //--- устанавливаем координаты такими же, как переданные в метод и уходим
      x=coord_x;
      y=coord_y;
      return;
     }
//--- Если это не первый объект в списке
//--- Если (нижняя граница прошлого объекта + 4 пикселя) ниже нижней границы панели ListBox (следующий объект выйдет за границы),
   if(obj.BottomEdge()+DEF_CONTROL_LIST_MARGIN_Y>this.BottomEdge())
     {
      //--- Если ширина колонок равно нулю, то координатой X создаваемого объекта будет правая граница прошлого объекта + 6 пикселей
      //--- Иначе, если ширина колонок больше нуля, то координатой X создаваемого объекта будет координата X прошлого + ширина колонки
      //--- Координатой Y будет переданное в метод значение (начинаем размещать объекты в новой колонке)
      x=(this.ColumnWidth()==0 ? obj.RightEdgeRelative()+DEF_CONTROL_LIST_MARGIN_X : int(obj.CoordXRelative()+this.ColumnWidth()));
      y=coord_y;
     }
//--- Если создаваемый объект помещается в пределы панели ListBox -
   else
     {
      //--- координатой X создаваемого объекта будет смещение прошлого от края панели минус ширина её рамки,
      //--- координатой Y будет нижняя граница прошлого, вышестоящего объекта плюс 4 пикселя
      x=obj.CoordXRelative()-this.BorderSizeLeft();
      y=obj.BottomEdgeRelative()+DEF_CONTROL_LIST_MARGIN_Y+(this.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX ? 2 : 0);
     }
  }
//+------------------------------------------------------------------+

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

public:
//--- Создаёт список из указанного количества строк (объектов Label)
   void              CreateList(const int line_count,const int new_column_width=0,const bool autosize=true);
//--- Конструктор

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

//+------------------------------------------------------------------+
//| Создаёт список из указанного количества строк (объектов Button)  |
//+------------------------------------------------------------------+
void CListBox::CreateList(const int count,const int new_column_width=0,const bool autosize=true)
  {
//--- Создаём указатель на объект Button
   CButton *obj=NULL;
//--- Рассчитываем ширину создаваемого объекта в зависимости от указанной ширины столбца
   int width=(new_column_width>0 ? new_column_width : this.Width()-this.BorderSizeLeft()-this.BorderSizeRight());
//--- Создаём указанное количество объектов Button
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,0,0,width,15,new_column_width,autosize);
//--- В цикле по созданному количеству объектов
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Получаем созданный объект из списка по индексу цикла
      obj=this.GetElement(i);
      //--- Если объект получить не удалось - выводим об этом сообщение в журнал и идём к следующему
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Устанавливаем выравнивание текста слева по центру
      obj.SetTextAlign(ANCHOR_LEFT);
      //--- Устанавливаем текст объекта
      obj.SetFontSize(8);
      obj.SetText(" ListBoxItem"+string(i+1));
      //--- Устанавливаем цвета фона, текста и рамки
      obj.SetBackgroundStateOnColor(clrDodgerBlue,true);
      obj.SetBackgroundStateOnColorMouseOver(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-5));
      obj.SetBackgroundStateOnColorMouseDown(obj.ChangeColorLightness(obj.BackgroundStateOnColor(),-10));
      obj.SetForeStateOnColor(this.BackgroundColor(),true);
      obj.SetForeStateOnColorMouseOver(obj.ChangeColorLightness(obj.ForeStateOnColor(),-5));
      obj.SetForeStateOnColorMouseDown(obj.ChangeColorLightness(obj.ForeStateOnColor(),-10));
      obj.SetBorderColor(obj.BackgroundColor(),true);
      obj.SetBorderColorMouseDown(obj.BackgroundColorMouseDown());
      obj.SetBorderColorMouseOver(obj.BackgroundColorMouseOver());
      //--- Ставим флаги кнопки-переключателя и групповой кнопки
      obj.SetToggleFlag(true);
      obj.SetGroupButtonFlag(true);
     }
//--- Если в метод передан флаг автоматического изменения размеров базового объекта -
//--- устанавливаем режим автоизменения размера как "увеличить и уменьшить"
   if(autosize)
      this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false);
  }
//+------------------------------------------------------------------+

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

Аналогичные доработки внесём в класс объекта CheckedListBox в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckedListBox.mqh.

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

public:
//--- Создаёт указанное количество объектов CheckBox
   void              CreateCheckBox(const int count,const int width,const int new_column_width=0,const bool autosize=true);
//--- Конструктор

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

//+------------------------------------------------------------------+
//| Создаёт указанное количество объектов CheckBox                   |
//+------------------------------------------------------------------+
void CCheckedListBox::CreateCheckBox(const int count,const int width,const int new_column_width=0,const bool autosize=true)
  {
//--- Создаём указатель на объект CheckBox
   CCheckBox *obj=NULL;
//--- Создаём указанное количество объектов CheckBox
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_CHECKBOX,count,2,2,width,DEF_CHECK_SIZE,new_column_width,autosize);
//--- В цикле по созданному количеству объектов
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Получаем созданный объект из списка по индексу цикла
      obj=this.GetElement(i);
      //--- Если объект получить не удалось - выводим об этом сообщение в журнал и идём к следующему
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CHECKBOX));
         continue;
        }
      //--- Устанавливаем выравнивание флажка проверки и текста слева по центру
      obj.SetCheckAlign(ANCHOR_LEFT);
      obj.SetTextAlign(ANCHOR_LEFT);
      //--- Устанавливаем текст объекта
      obj.SetText("CheckBox"+string(i+1));
     }
//--- Если в метод передан флаг автоматического изменения размеров базового объекта -
//--- устанавливаем режим автоизменения размера как "увеличить и уменьшить"
   if(autosize)
      this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false);
  }
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CCheckedListBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                                const int obj_num,
                                                const string obj_name,
                                                const int x,
                                                const int y,
                                                const int w,
                                                const int h,
                                                const color colour,
                                                const uchar opacity,
                                                const bool movable,
                                                const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
//--- создаём объект CheckBox
   CGCnvElement *element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h+3);
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
//--- установим объекту флаг перемещаемости и относительные координаты
   element.SetMovable(movable);
   element.SetCoordXRelative(element.CoordX()-this.CoordX());
   element.SetCoordYRelative(element.CoordY()-this.CoordY());
   return element;
  }
//+------------------------------------------------------------------+

Аналогичным образом доработан и класс объекта ButtonListBox в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ButtonListBox.mqh.

Так же добавлен флаг автоматического изменения размера контейнера под содержимое:

public:
//--- Создаёт указанное количество объектов CheckBox
   void              CreateButton(const int count,const int width,const int height,const int new_column_width=0,const bool autosize=true);
//--- Конструктор

а в коде реализации этого метода этот флаг передаётся в метод создания объектов, а затем проверяется для автоматического изменения размера контейнера под созданное содержимое:

//+------------------------------------------------------------------+
//| Создаёт указанное количество объектов Button                     |
//+------------------------------------------------------------------+
void CButtonListBox::CreateButton(const int count,const int width,const int height,const int new_column_width=0,const bool autosize=true)
  {
//--- Создаём указатель на объект Button
   CButton *obj=NULL;
//--- Создаём указанное количество объектов Button
   CElementsListBox::CreateElements(GRAPH_ELEMENT_TYPE_WF_BUTTON,count,2,2,width,height,new_column_width,autosize);
//--- В цикле по созданному количеству объектов
   for(int i=0;i<this.ElementsTotal();i++)
     {
      //--- Получаем созданный объект из списка по индексу цикла
      obj=this.GetElement(i);
      //--- Если объект получить не удалось - выводим об этом сообщение в журнал и идём к следующему
      if(obj==NULL)
        {
         ::Print(DFUN,MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ,this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Устанавливаем выравнивание текста слева по центру
      obj.SetTextAlign(ANCHOR_CENTER);
      //--- Устанавливаем текст объекта
      obj.SetText("Button"+string(i+1));
     }
//--- Если в метод передан флаг автоматического изменения размеров базового объекта -
//--- устанавливаем режим автоизменения размера как "увеличить и уменьшить"
   if(autosize)
      this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK,false);
  }
//+------------------------------------------------------------------+

Макет WinForms-объекта TabControl

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

Объект TabControl должен состоять из панели, на которой будут размещаться вкладки элемента управления (объекты TabPage), которые будут состоять из кнопки и панели. Кнопкой (заголовок вкладки — TabHeader) вкладка будет активизироваться, а на панели будем размещать те объекты, которые должны располагаться на ней. При активировании одной вкладки её панель отображается, кнопка становится чуть больше в размерах, чем кнопки неактивных вкладок, панели которых скрываются.

Так как заголовок вкладки (объект TabHeader) должен работать как кнопка, и при этом уметь увеличиваться в размерах, а его внешний вид немного отличаться от элемента управления Button, то, соответственно, этот объект должен быть наследником элемента управления "Кнопка", в котором будет реализован дополнительный функционал.

Объект "Вкладка" (TabPage) должен быть объектом-контейнером, так как в нём нужно разместить как кнопку-заголовок, так и далее прикреплять к нему другие объекты. Вероятнее всего, это будет объект "Панель", к которой будет прикреплена кнопка-заголовок, а на самой панели можно будет размещать прикрепляемые элементы.

Объект TabControl должен быть объектом-панель, к которой прикреплены объекты-вкладки.

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

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

В папке бибилотеки \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\ создадим новый файл TabControl.mqh класса TabControl.

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

//+------------------------------------------------------------------+
//|                                                   TabControl.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 "..\Containers\Container.mqh"
#include "..\Containers\GroupBox.mqh"
//+------------------------------------------------------------------+

и ниже впишем классы-пустышки — заготовки двух классов для создания заголовка вкладки и вкладки:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\Containers\Container.mqh"
#include "..\Containers\GroupBox.mqh"
//+------------------------------------------------------------------+
//| Класс объекта TabHeader элементов управления WForms TabControl   |
//+------------------------------------------------------------------+
class CTabHeader : public CButton
  {
private:

protected:

public:

//--- Конструктор
                     CTabHeader(const long chart_id,
                             const int subwindow,
                             const string name,
                             const int x,
                             const int y,
                             const int w,
                             const int h);
  };
//+------------------------------------------------------------------+
//| CTabHeader::Конструктор                                          |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const long chart_id,
                       const int subwindow,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
  }
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Класс объекта TabPage элементов управления WForms TabControl     |
//+------------------------------------------------------------------+
class CTabPage : public CContainer
  {
private:

public:

//--- Конструктор
                     CTabPage(const long chart_id,
                              const int subwindow,
                              const string name,
                              const int x,
                              const int y,
                              const int w,
                              const int h);
  };
//+------------------------------------------------------------------+
//| CTabPage::Конструктор с указанием идентификатора чарта и подокна |
//+------------------------------------------------------------------+
CTabPage::CTabPage(const long chart_id,
                   const int subwindow,
                   const string name,
                   const int x,
                   const int y,
                   const int w,
                   const int h) : CContainer(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_PAGE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_PAGE);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
  }
//+------------------------------------------------------------------+

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

Далее объявим класс WinForms-объекта TabControl, унаследованный от класса объекта-контейнера, и разместим в нём объявления переменных и методов для работы с классом:

//+------------------------------------------------------------------+
//| Класс объекта TabControl элементов управления WForms             |
//+------------------------------------------------------------------+
class CTabControl : public CContainer
  {
private:
   int                  m_item_width;                 // Фиксированная ширина заголовков вкладок
   int                  m_item_height;                // Фиксированная высота заголовков вкладок
//--- Создаёт новый графический объект
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
public:
//--- Создаёт указанное количество объектов TabPage
   void              CreateTabPage(const int count,const int width,const int height,const int tab_state=1);

//--- (1) Устанавливает, (2) возвращает местоположение заголовков вкладок на элементе управления
   void              SetAlignment(const ENUM_CANV_ELEMENT_ALIGNMENT alignment)   { this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,alignment); }
   ENUM_CANV_ELEMENT_ALIGNMENT Alignment(void)  const { return (ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);  }
//--- (1) Устанавливает, (2) возвращает флаг разрешения нескольких рядов заголовков вкладок на элементе управления
   void              SetMultiline(const bool flag)    { this.SetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE,flag);                                 }
   bool              Multiline(void)            const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TAB_MULTILINE);                         }
//--- (1) Устанавливает, (2) возвращает фиксированную ширину заголовков вкладок
   void              SetItemWidth(const int value)    { this.m_item_width=value;    }
   int               ItemWidth(void)            const { return this.m_item_width;   }
//--- (1) Устанавливает, (2) возвращает фиксированную высоту заголовков вкладок
   void              SetItemHeight(const int value)   { this.m_item_height=value;   }
   int               ItemHeight(void)           const { return this.m_item_height;  }
//--- Устанавливает фиксированный размер заголовков вкладок
   void              SetItemSize(const int w,const int h)
                       {
                        if(this.ItemWidth()!=w)
                           this.SetItemWidth(w);
                        if(this.ItemHeight()!=h)
                           this.SetItemHeight(h);
                       }
//--- Конструктор
                     CTabControl(const long chart_id,
                                 const int subwindow,
                                 const string name,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h);
  };
//+------------------------------------------------------------------+

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

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CTabControl::CTabControl(const long chart_id,
                         const int subwindow,
                         const string name,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CContainer(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL);
   this.SetID(this.GetMaxIDAll()+1);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetOpacity(CLR_DEF_CONTROL_TAB_OPACITY,true);
   this.SetBackgroundColor(CLR_DEF_CONTROL_TAB_BACK_COLOR,true);
   this.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_MOUSE_DOWN);
   this.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_MOUSE_OVER);
   this.SetBorderColor(CLR_DEF_CONTROL_TAB_BORDER_COLOR,true);
   this.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_BORDER_MOUSE_DOWN);
   this.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_BORDER_MOUSE_OVER);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetMultiline(false);
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetItemSize(58,20);
   this.CreateTabPage(3,this.Width(),this.Height()-this.ItemHeight(),0);
  }
//+------------------------------------------------------------------+

В конце кода конструктора вызовем метод для создания трёх вкладок.

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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                            const int obj_num,
                                            const string obj_name,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            const color colour,
                                            const uchar opacity,
                                            const bool movable,
                                            const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT              :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM                 :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
         element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
         element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
         element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
         element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
         element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX  :
         element=new CListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER       :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE       :
         element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

Метод идентичен точно таким же методам создания графических элементов в других классах объектов-контейнеров и просто создаёт новые графические элементы в соответствии с переданным в метод типом.

Метод, создающий указанное количество объектов TabPage:

//+------------------------------------------------------------------+
//| Создаёт указанное количество объектов TabPage                    |
//+------------------------------------------------------------------+
void CTabControl::CreateTabPage(const int count,const int width,const int height,const int tab_state=1)
  {
//--- Создаём указатель на объекты Button и Container
   CButton *header=NULL;
   CContainer *tab=NULL;
//--- Создаём указанное количество объектов TabPage
   for(int i=0;i<count;i++)
     {
      //--- Указываем начальные координаты вкладок
      int x=this.BorderSizeLeft()+2;
      int y=this.BorderSizeTop()+2;
      //--- Создаём объект-кнопку в качестве заголовка вкладки
      if(!CContainer::CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BUTTON,x+(this.ItemWidth()-4)*i,y,this.ItemWidth()-4,this.ItemHeight()-2,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Получаем созданную кнопку из списка созданных объектов
      header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,i);
      if(header==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_BUTTON));
         continue;
        }
      //--- Устанавливаем для заголовка значения его свойств по умолчанию
      header.SetID(this.GetMaxIDAll()+1);
      header.SetToggleFlag(true);
      header.SetGroupButtonFlag(true);
      header.SetText("TabPage"+string(i+1));
      header.SetTextAlign(ANCHOR_CENTER);
      header.SetOpacity(CLR_DEF_CONTROL_TAB_HEAD_OPACITY,true);
      header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
      header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
      header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
      header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
      header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
      header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
      header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
      header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
      header.SetForeColor(CLR_DEF_FORE_COLOR,true);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_TOP)
         header.SetBorderSize(1,1,1,0);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_BOTTOM)
         header.SetBorderSize(1,0,1,1);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
         header.SetBorderSize(1,1,0,1);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
         header.SetBorderSize(0,1,1,1);
      
      //--- Создаём объект-контейнер в качестве поля вкладки, на котором будут размещаться прикрепляемые объекты
      if(!CContainer::CreateNewElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER,x-2,header.BottomEdgeRelative(),width,height,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER));
         continue;
        }
      //--- Получаем вкладку из списка созданных объектов
      tab=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,i);
      if(tab==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_CONTAINER));
         continue;
        }
      //--- Устанавливаем значения свойств по умолчанию для созданной вкладки
      tab.SetID(this.GetMaxIDAll()+1);
      tab.SetBorderSizeAll(1);
      tab.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      tab.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      tab.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      tab.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      tab.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      tab.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      tab.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      tab.SetForeColor(CLR_DEF_FORE_COLOR,true);
      tab.Hide();
     }
   //--- Получаем заголовок и вкладку из списка по номеру активной вкладки
   header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BUTTON,tab_state);
   tab=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,tab_state);
   //--- Если указатели на объекты получены
   if(header!=NULL && tab!=NULL)
     {
      //--- Отображаем вкладку
      tab.Show();
      //--- Перемещаем заголовок на передний план и устанавливаем для него новые размеры
      header.BringToTop();
      header.SetState(true);
      header.SetWidth(this.ItemWidth());
      header.SetHeight(this.ItemHeight());
      //--- Смещаем заголовок на новые координаты, так как размер стал чуть больше,
      //--- и устанавливаем новые относительные координаты для заголовка
      header.Move(header.CoordX()-2,header.CoordY()-2);
      header.SetCoordXRelative(header.CoordXRelative()-2);
      header.SetCoordYRelative(header.CoordYRelative()-2);
      header.Update(true);
     }
  }
//+------------------------------------------------------------------+

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

Это пока всё, что нам нужно для отображения макета элемента управления TabControl.

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

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

//+------------------------------------------------------------------+
//| Устанавливает параметры присоединённому объекту                  |
//+------------------------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
//--- Устанавливаем объекту цвет текста как у базового контейнера
   obj.SetForeColor(this.ForeColor(),true);
//--- Если созданный объект не является контейнером - устанавливаем для него такую же группу, как у его базового объекта
   if(obj.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_CONTAINER || obj.TypeGraphElement()>GRAPH_ELEMENT_TYPE_WF_GROUPBOX)
      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"
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
        //--- устанавливаем цвет текста объекта как цвет текста контейнера в зависимости от переданного в метод:
        //--- цвет фона устанавливаем  в зависимости от переданного в метод:
        //--- либо цвет фона стандартных элементов управления по умолчанию, либо переданный в метод.
        //--- Цвет рамки устанавливаем равным цвету текста
        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;
      case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE          :
        //--- устанавливаем цвет текста объекта как цвет текста контейнера в зависимости от переданного в метод:
        //--- цвет фона устанавливаем  в зависимости от переданного в метод:
        //--- либо цвет фона стандартных элементов управления по умолчанию, либо переданный в метод.
        //--- Цвет рамки устанавливаем равным цвету текста
        obj.SetBackgroundColor(colour==clrNONE ? CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR : colour,true);
        obj.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
        obj.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
        obj.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
        obj.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
        obj.SetForeColor(CLR_DEF_FORE_COLOR,true);
        obj.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY);
        obj.SetBorderSizeAll(1);
        obj.SetBorderStyle(FRAME_STYLE_NONE);
        break;
      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;
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

К файлу \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh класса объекта-панели подключим файл объекта TabControl:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "GroupBox.mqh"
#include "TabControl.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
//+------------------------------------------------------------------+

Теперь этот класс станет видимым для всех классов объектов-контейнеров.

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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CPanel::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const int obj_num,
                                       const string obj_name,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h,
                                       const color colour,
                                       const uchar opacity,
                                       const bool movable,
                                       const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
         element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
         element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
         element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
         element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
         element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX          :
         element=new CListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE          :
         element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

Здесь всё стандартно для таких методов — в зависимости от переданного в метод типа создаётся соответствующий объект.

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\GroupBox.mqh класса объекта GroupBox, точно так же в методе для создания нового графического объекта, добавим создание объектов класса TabControl:

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CGroupBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int obj_num,
                                          const string obj_name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity)
  {
   string name=this.CreateNameDependentObject(obj_name);
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_ELEMENT              :
         element=new CGCnvElement(type,this.ID(),obj_num,this.ChartID(),this.SubWindow(),name,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM                 :
         element=new CForm(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER         :
         element=new CContainer(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX          :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL             :
         element=new CPanel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL             :
         element=new CLabel(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX          :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON       :
         element=new CRadioButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON            :
         element=new CButton(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX  :
         element=new CListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX  :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX   :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER        :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_PAGE          :
         element=new CTabPage(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL       :
         element=new CTabControl(this.ChartID(),this.SubWindow(),name,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),": ",name);
   return element;
  }
//+------------------------------------------------------------------+

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

//--- Сбрасывает всем формам флаги взаимодействия кроме указанной
   void              ResetAllInteractionExeptOne(CGCnvElement *form);
//--- Постобработка бывшей активной формы под курсором
   void              FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam);
//--- Добавляет элемент в список-коллекцию

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

//+------------------------------------------------------------------+
//| Постобработка бывшей активной формы под курсором                 |
//+------------------------------------------------------------------+
void CGraphElementsCollection::FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam)
  {
//--- Получаем главный объект, к которому прикреплена форма
   CForm *main=form.GetMain();
   if(main==NULL)
      main=form;
//--- Получаем все элементы, прикреплённые к форме
   CArrayObj *list=main.GetListElements();
   if(list==NULL)
      return;
   //--- В цикле по списку полученных элементов
   int total=list.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем указатель на объект
      CForm *obj=list.At(i);
      //--- если указатель получить не удалось - идём к следующему объекту в списке
      if(obj==NULL)
         continue;
      obj.OnMouseEventPostProcessing();
      //--- Создаём список объектов взаимодействия объекта и получаем их количество
      int count=obj.CreateListInteractObj();
      //--- В цикле по полученному списку
      for(int j=0;j<count;j++)
        {
         //--- получаем очередной объект
         CWinFormBase *elm=obj.GetInteractForm(j);
         if(elm==NULL)
            continue;
         //--- определяем расположение курсора относительно объекта 
         //--- и вызываем для объекта метод обработки событий мышки
         elm.MouseFormState(id,lparam,dparam,sparam);
         elm.OnMouseEventPostProcessing();
        }
     }
   ::ChartRedraw(main.ChartID());
  }
//+------------------------------------------------------------------+

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

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

            else
              {
               //--- Если в mouse_state состояние мышки неопределённое - это отжатие левой кнопки
               //--- Присваиваем переменной новое состояние мышки
               if(mouse_state==MOUSE_FORM_STATE_NONE)
                  mouse_state=MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED;
               //--- Обработаем увод курсора мышки с графического элемента
               this.FormPostProcessing(form,id,lparam,dparam,sparam);
              }

У нас всё готово для тестирования.


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

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

Вместо создания объектов CheckedListBox, ButtonListBox и ListBox на втором элементе управления GroupBox, создадим объект TabControl:

      //--- Если прикреплённый объект GroupBox создан
      if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,x,2,w,h,C'0x91,0xAA,0xAE',0,true,false))
        {
         //--- получим указатель на объект GroupBox по его индексу в списке прикреплённых объектов с типом GroupBox
         gbox2=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,1);
         if(gbox2!=NULL)
           {
            //--- установим тип рамки "вдавленная рамка", цвет рамки как цвет фона основной панели,
            //--- а цвет текста - затемнённый на 1 цвет фона последней прикреплённой панели
            gbox2.SetBorderStyle(FRAME_STYLE_STAMP);
            gbox2.SetBorderColor(pnl.BackgroundColor(),true);
            gbox2.SetForeColor(gbox2.ChangeColorLightness(obj.BackgroundColor(),-1),true);
            gbox2.SetText("GroupBox2");
            
            //--- Создадим объект TabControl
            gbox2.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,4,12,gbox2.Width()-12,gbox2.Height()-20,clrNONE,255,true,false);
            //--- получим указатель на объект TabControl по его индексу в списке прикреплённых объектов с типом TabControl
            CTabControl *tctrl=gbox2.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
            if(tctrl!=NULL)
              {
               //--- получим указатель на объект Container по его индексу в списке прикреплённых объектов с типом Container
               CContainer *page=tctrl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_CONTAINER,0);
               if(page!=NULL)
                 {
                  // Здесь будем создавать объекты, прикреплённые к указанной вкладке объекта TabControl
                  // К сожалению, в текущем состоянии создания имён графических объектов библиотеки
                  // дальнейшее их создание упирается в предел количества символов в имени ресурса в классе CCanvas
                 }
               
              }
            /*
            //--- Создадим объект CheckedListBox
            //---...
            //---...
           }

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

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


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


Что дальше

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

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

К содержанию

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

DoEasy. Элементы управления (Часть 1): Первые шаги
DoEasy. Элементы управления (Часть 2): Продолжаем работу над классом CPanel
DoEasy. Элементы управления (Часть 3): Создание привязанных элементов управления
DoEasy. Элементы управления (Часть 4): Элемент управления "Панель", параметры Padding и Dock
DoEasy. Элементы управления (Часть 5): Базовый WinForms-объект, элемент управления "Панель", параметр AutoSize
DoEasy. Элементы управления (Часть 6): Элемент управления "Панель", автоизменение размеров контейнера под внутреннее содержимое
DoEasy. Элементы управления (Часть 7): Элемент управления "Текстовая метка"
DoEasy. Элементы управления (Часть 8): Базовые WinForms-объекты по категориям, элементы управления "GroupBox" и "CheckBox
DoEasy. Элементы управления (Часть 9): Реорганизация методов WinForms-объектов, элементы управления "RadioButton" и "Button"
DoEasy. Элементы управления (Часть 10): WinForms-объекты — оживляем интерфейс
DoEasy. Элементы управления (Часть 11): WinForms-объекты — группы, WinForms-объект CheckedListBox
DoEasy. Элементы управления (Часть 12): Базовый объект-список, WinForms-объекты ListBox и ButtonListBox