DoEasy. Элементы управления (Часть 26): Дорабатываем WinForms-объект "ToolTip" и начинаем разработку "ProgressBar"

Artyom Trishkin | 11 ноября, 2022

Содержание


Концепция

В прошлой статье мы начали создавать элемент управления ToolTip. Всплывающая подсказка должна появляться при наведении курсора на объект после небольшой паузы. При этом предыдущая показанная подсказка должна скрыться. Если после появления подсказки оставить курсор в прежнем положении, то по истечении определённого времени подсказка должна скрыться. Всё, что мы сделали в предыдущей статье это моментальное появление подсказки сразу же после наведения курсора на объект, которому она назначена. Сегодня продолжим работу над поведением всплывающей подсказки и сделаем так, чтобы оно больше соответствовало установленному в MS Visual Studio поведению программ, скомпилированных в этой студии. При наведении курсора на объект должна быть выдержана некоторая пауза, после которой всплывающая подсказка плавно появляется на экране. Если убрать курсор с объекта, то подсказка скрывается. Если оставить курсор на объекте, то по истечении некоторого времени подсказка плавно исчезает. Для реализации такого поведения нам нужно будет использовать таймер, созданный для графических элементов коллекции. У каждого объекта будет определён виртуальный обработчик событий таймера. Реализация таймера должна осуществляться конкретно в классах тех объектов, которые должны в нём обрабатываться.

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

После завершения доработки WinForms-объекта ToolTip, приступим к созданию элемента управления ProgressBar (элемент управления Windows "индикатор выполнения"). Создадим лишь статическую версию объекта — его свойства и внешний вид. Реализацией его работы займёмся уже в следующей статье.


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

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

//--- Параметры таймера коллекции графических объектов
#define COLLECTION_GRAPH_OBJ_PAUSE        (250)                   // Пауза таймера коллекции графических объектов в миллисекундах
#define COLLECTION_GRAPH_OBJ_COUNTER_STEP (16)                    // Шаг приращения счётчика таймера графических объектов
#define COLLECTION_GRAPH_OBJ_COUNTER_ID   (10)                    // Идентификатор счётчика таймера графических объектов
//--- Параметры таймера коллекции графических элементов на канвасе
#define COLLECTION_GRAPH_ELM_PAUSE        (16)                    // Пауза таймера коллекции графических элементов в миллисекундах
#define COLLECTION_GRAPH_ELM_COUNTER_STEP (16)                    // Шаг приращения счётчика таймера графических элементов
#define COLLECTION_GRAPH_ELM_COUNTER_ID   (11)                    // Идентификатор счётчика таймера графических элементов

//--- Идентификаторы списков коллекций

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

Для нового элемента управления ProgressBar добавим свои значения по умолчанию:

#define CLR_DEF_CONTROL_HINT_BACK_COLOR               (C'0xFF,0xFF,0xE1')  // Цвет фона элемента управления Hint
#define CLR_DEF_CONTROL_HINT_BORDER_COLOR             (C'0x76,0x76,0x76')  // Цвет рамки элемента управления Hint
#define CLR_DEF_CONTROL_HINT_FORE_COLOR               (C'0x5A,0x5A,0x5A')  // Цвет текста элемента управления Hint

#define CLR_DEF_CONTROL_PROGRESS_BAR_BACK_COLOR       (C'0xF0,0xF0,0xF0')  // Цвет фона элемента управления ProgressBar
#define CLR_DEF_CONTROL_PROGRESS_BAR_BORDER_COLOR     (C'0xBC,0xBC,0xBC')  // Цвет рамки элемента управления ProgressBar
#define CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR       (C'0x00,0x78,0xD7')  // Цвет текста элемента управления ProgressBar
#define CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR        (C'0x06,0xB0,0x25')  // Цвет линии прогресса элемента управления ProgressBar


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

//+------------------------------------------------------------------+
//| Список типов объектов библиотеки                                 |
//+------------------------------------------------------------------+
enum ENUM_OBJECT_DE_TYPE
  {
//--- Графика
   OBJECT_DE_TYPE_GBASE =  COLLECTION_ID_LIST_END+1,              // Тип объекта "Базовый объект всех графических объектов библиотеки"
   OBJECT_DE_TYPE_GELEMENT,                                       // Тип объекта "Графический элемент"
   OBJECT_DE_TYPE_GFORM,                                          // Тип объекта "Форма"
   OBJECT_DE_TYPE_GFORM_CONTROL,                                  // Тип объекта "Форма управления опорными точками графического объекта"
   OBJECT_DE_TYPE_GSHADOW,                                        // Тип объекта "Тень"
   OBJECT_DE_TYPE_GGLARE,                                         // Тип объекта "Блик"
   //--- WinForms
   OBJECT_DE_TYPE_GWF_BASE,                                       // Тип объекта "WinForms Base" (базовый абстрактный WinForms-объект)
   OBJECT_DE_TYPE_GWF_CONTAINER,                                  // Тип объекта "WinForms контейнер"
   OBJECT_DE_TYPE_GWF_COMMON,                                     // Тип объекта "WinForms станартный элемент управления"
   OBJECT_DE_TYPE_GWF_HELPER,                                     // Тип объекта "WinForms вспомогательный элемент управления"
//--- Анимация


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

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


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

//+------------------------------------------------------------------+
//| Список предопределённых значков-иконок                           |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_TOOLTIP_ICON
  {
   CANV_ELEMENT_TOOLTIP_ICON_NONE,                    // None
   CANV_ELEMENT_TOOLTIP_ICON_INFO,                    // Info
   CANV_ELEMENT_TOOLTIP_ICON_WARNING,                 // Warning
   CANV_ELEMENT_TOOLTIP_ICON_ERROR,                   // Error
   CANV_ELEMENT_TOOLTIP_ICON_USER,                    // User
  };
//+------------------------------------------------------------------+
//| Список состояний отображения элемента управления                 |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_DISPLAY_STATE
  {
   CANV_ELEMENT_DISPLAY_STATE_NORMAL,                 // Нормально
   CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN,        // Ожидание процесса постепенного появления
   CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_IN,        // Процесс постепенного появления
   CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_IN,      // Завершение процесса постепенного появления
   CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_OUT,       // Ожидание процесса постепенного исчезания
   CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_OUT,       // Процесс постепенного исчезания
   CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_OUT,     // Завершение процесса постепенного исчезания
   CANV_ELEMENT_DISPLAY_STATE_COMPLETED,              // Завершение процесса обработки
  };
//+------------------------------------------------------------------+

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

Индикатор выполнения ProgressBar может иметь три стиля начертания линии прогресса. Опишем их в перечислении:

//+------------------------------------------------------------------+
//| Список стилей элемента ProgressBar                               |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROGRESS_BAR_STYLE
  {
   CANV_ELEMENT_PROGRESS_BAR_STYLE_BLOCKS,            // Сегментированные блоки
   CANV_ELEMENT_PROGRESS_BAR_STYLE_CONTINUOUS,        // Непрерывная полоса
   CANV_ELEMENT_PROGRESS_BAR_STYLE_MARQUEE,           // Непрерывная прокрутка
  };
//+------------------------------------------------------------------+
//| Целочисленные свойства графического элемента на канвасе          |
//+------------------------------------------------------------------+

Сегодня будет сделан только один стиль — непрерывная полоса.


Состояние, в котором находится объект, если для него реализовано анимированное отображение, нужно сохранять и получать из свойств объекта.
Добавим новые свойства графического элемента в перечисление целочисленных свойств графического элемента на канвасе и увеличим общее количество целочисленных свойств со 129 до 138:

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

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

   CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,           // Ширина области верхней грани
   CANV_ELEMENT_PROP_DISPLAYED,                       // Флаг отображения не скрытого элемента управления
   CANV_ELEMENT_PROP_DISPLAY_STATE,                   // Состояние отображения элемента управления
   CANV_ELEMENT_PROP_DISPLAY_DURATION,                // Продолжительность процесса отображения элемента управления
   CANV_ELEMENT_PROP_GROUP,                           // Группа, к которой принадлежит графический элемент
   CANV_ELEMENT_PROP_ZORDER,                          // Приоритет графического объекта на получение события нажатия мышки на графике

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

   CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,              // Подсказка в форме "облачка"
   CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,              // Угасание при отображении и скрытии подсказки
   CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM,            // Верхняя граница диапазона, в котором действует ProgressBar
   CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM,            // Нижняя граница диапазона, в котором действует ProgressBar
   CANV_ELEMENT_PROP_PROGRESS_BAR_STEP,               // Величина приращения значения ProgressBar для его перерисовки
   CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE,              // Стиль элемента ProgressBar
   CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE,              // Текущее начение элемента ProgressBar в диапазоне от Min до Max
   CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED, // Скорость анимации полосы прогресса при стиле Marquee
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (138)         // Общее количество целочисленных свойств
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Количество неиспользуемых в сортировке целочисленных свойств
//+------------------------------------------------------------------+


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

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

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

   SORT_BY_CANV_ELEMENT_BORDER_TOP_AREA_WIDTH,        // Сортировать по ширине области верхней грани
   SORT_BY_CANV_ELEMENT_DISPLAYED,                    // Сортировать по флагу отображения не скрытого элемента управления
   SORT_BY_CANV_ELEMENT_DISPLAY_STATE,                // Сортировать по состоянию отображения элемента управления
   SORT_BY_CANV_ELEMENT_DISPLAY_DURATION,             // Сортировать по продолжительности процесса отображения элемента управления
   SORT_BY_CANV_ELEMENT_GROUP,                        // Сортировать по группе, к которой принадлежит графический элемент
   SORT_BY_CANV_ELEMENT_ZORDER,                       // Сортировать по приоритету графического объекта на получение события нажатия мышки на графике

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

   SORT_BY_CANV_ELEMENT_TOOLTIP_IS_BALLOON,           // Сортировать по флагу подсказки в форме "облачка"
   SORT_BY_CANV_ELEMENT_TOOLTIP_USE_FADING,           // Сортировать по флагу угасания при отображении и скрытии подсказки
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_MAXIMUM,         // Сортировать по верхней границе диапазона, в котором действует ProgressBar
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_MINIMUM,         // Сортировать по нижней границе диапазона, в котором действует ProgressBar
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_STEP,            // Сортировать по вличине приращения значения ProgressBar для его перерисовки
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_STYLE,           // Сортировать по стилю элемента ProgressBar
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_VALUE,           // Сортировать по текущему начению элемента ProgressBar в диапазоне от Min до Max
   SORT_BY_CANV_ELEMENT_PROGRESS_BAR_MARQUEE_ANIM_SPEED, // Сортировать по скорости анимации полосы прогресса при стиле Marquee
//--- Сортировка по вещественным свойствам

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

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


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

   MSG_LIB_TEXT_CHEK_STATE_UNCHECKED,                 // Не установлен
   MSG_LIB_TEXT_CHEK_STATE_CHECKED,                   // Установлен
   MSG_LIB_TEXT_CHEK_STATE_INDETERMINATE,             // Неопределённый
   
   MSG_LIB_TEXT_ICON_NONE,                            // None
   MSG_LIB_TEXT_ICON_INFO,                            // Info
   MSG_LIB_TEXT_ICON_WARNING,                         // Warning
   MSG_LIB_TEXT_ICON_ERROR,                           // Error
   MSG_LIB_TEXT_ICON_USER,                            // User
   MSG_LIB_TEXT_STYLE_BLOCKS,                         // Сегментированные блоки
   MSG_LIB_TEXT_STYLE_CONTINUOUS,                     // Непрерывная полоса
   MSG_LIB_TEXT_STYLE_MARQUEE,                        // Непрерывная прокрутка
   
   MSG_LIB_TEXT_SUNDAY,                               // Воскресение
   MSG_LIB_TEXT_MONDAY,                               // Понедельник

...

   MSG_GRAPH_ELEMENT_TYPE_ELEMENT,                    // Элемент
   MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                 // Объект тени
   MSG_GRAPH_ELEMENT_TYPE_GLARE_OBJ,                  // Объект блика
   MSG_GRAPH_ELEMENT_TYPE_FORM,                       // Форма
   MSG_GRAPH_ELEMENT_TYPE_WINDOW,                     // Окно

...

   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,          // Элемент управления HintMoveLeft
   MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP,                 // Элемент управления ToolTip
   MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,        // Элемент управления BarProgressBar
   MSG_GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR,            // Элемент управления ProgressBar
//---
   MSG_GRAPH_ELEMENT_TYPE_WF_WRONG_TYPE_PASSED,       // Передан не правильный тип элемента управления
   MSG_GRAPH_ELEMENT_TYPE_WF_CANT_ADD_2_TOOLTIP,      // Нельзя к одному элементу управления добавить два и более ToolTip
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Графический объект принадлежит программе
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Графический объект не принадлежит программе

...

//--- CGraphElementsCollection
   MSG_GRAPH_OBJ_FAILED_GET_ADDED_OBJ_LIST,           // Не удалось получить список вновь добавленных объектов
   MSG_GRAPH_OBJ_FAILED_GET_ACTIVE_OBJ_LIST,          // Не удалось получить список активных элементов
   MSG_GRAPH_OBJ_FAILED_GET_OBJECT_NAMES,             // Не удалось получить имена объектов
   MSG_GRAPH_OBJ_FAILED_DETACH_OBJ_FROM_LIST,         // Не удалось изъять графический объект из списка

...

   MSG_CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,       // Ширина области верхней грани
   MSG_CANV_ELEMENT_PROP_DISPLAYED,                   // Флаг отображения не скрытого элемента управления
   MSG_CANV_ELEMENT_PROP_DISPLAY_STATE,               // Состояние отображения элемента управления
   MSG_CANV_ELEMENT_PROP_DISPLAY_DURATION,            // Продолжительность процесса отображения элемента управления
   MSG_CANV_ELEMENT_PROP_ENABLED,                     // Флаг доступности элемента
   MSG_CANV_ELEMENT_PROP_FORE_COLOR,                  // Цвет текста по умолчанию для всех объектов элемента управления

...

   MSG_CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,          // Подсказка в форме "облачка"
   MSG_CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,          // Угасание при отображении и скрытии подсказки
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM,        // Верхняя граница диапазона, в котором действует ProgressBar
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM,        // Нижняя граница диапазона, в котором действует ProgressBar
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_STEP,           // Величина приращения значения ProgressBar для его перерисовки
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE,          // Стиль элемента ProgressBar
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE,          // Текущее начение элемента ProgressBar в диапазоне от Min до Max
   MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED,// Скорость анимации полосы прогресса при стиле Marquee
   
//--- Вещественные свойства графических элементов

//--- Строковые свойства графических элементов
   MSG_CANV_ELEMENT_PROP_NAME_OBJ,                    // Имя объекта-графического элемента
   MSG_CANV_ELEMENT_PROP_NAME_RES,                    // Имя графического ресурса
   MSG_CANV_ELEMENT_PROP_TEXT,                        // Текст графического элемента
   MSG_CANV_ELEMENT_PROP_DESCRIPTION,                 // Описание графического элемента
   MSG_CANV_ELEMENT_PROP_TOOLTIP_TITLE,               // Заголовок подсказки элемента
   MSG_CANV_ELEMENT_PROP_TOOLTIP_TEXT,                // Текст подсказки элемента
  };
//+------------------------------------------------------------------+

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

   {"Не установлен","Unchecked"},
   {"Установлен","Checked"},
   {"Неопределённый","Indeterminate"},
   
   {"None","None"},
   {"Info","Info"},
   {"Warning","Warning"},
   {"Error","Error"},
   {"Пользовательский","User"},
   {"Сегментированные блоки","Blocks"},
   {"Непрерывная полоса","Continuous"},
   {"Непрерывная прокрутка","Marquee"},
   
   {"Воскресение","Sunday"},
   {"Понедельник","Monday"},

...

   {"Элемент","Element"},
   {"Объект тени","Shadow object"},
   {"Объект блика","Glare object"},
   {"Форма","Form"},
   {"Окно","Window"},

...

   {"Элемент управления \"HintMoveDown\"","Control element \"HintMoveDown\""},
   {"Элемент управления \"ToolTip\"","Control element \"ToolTip\""},
   {"Элемент управления BarProgressBar","Control element \"BarProgressBar\""},
   {"Элемент управления ProgressBar","Control element \"ProgressBar\""},
   
   {"Передан не правильный тип элемента управления","Wrong control type passed"},
   {"Нельзя к одному элементу управления добавить два и более ToolTip","Can't add two or more ToolTips to one control"},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},

...

//--- CGraphElementsCollection
   {"Не удалось получить список вновь добавленных объектов","Failed to get the list of newly added objects"},
   {"Не удалось получить список активных элементов","Failed to get list of active elements"},
   {"Не удалось получить имена объектов","Failed to get object names"},
   {"Не удалось изъять графический объект из списка","Failed to detach graphic object from the list"},

...

   {"Ширина области верхней грани","Width of the top border area"},
   {"Флаг отображения не скрытого элемента управления","Flag that sets the display of a non-hidden control"},
   {"Состояние отображения элемента управления","Display state of the control"},
   {"Продолжительность процесса отображения элемента управления","Duration of the process of displaying the control"},
   {"Флаг доступности элемента","Element Availability Flag"},
   {"Цвет текста по умолчанию для всех объектов элемента управления","Default text color for all objects in the control"},

...

   {"Подсказка в форме \"облачка\"","Tooltip as \"Balloon\""},
   {"Угасание при отображении и скрытии подсказки","Tooltip uses fading"},
   {"Верхняя граница диапазона, в котором действует ProgressBar","Upper bound of the range in which the ProgressBar operates"},
   {"Нижняя граница диапазона, в котором действует ProgressBar","Lower bound of the range in which the ProgressBar operates"},
   {"Величина приращения значения ProgressBar для его перерисовки","Increment value of the ProgressBar to redraw it"},
   {"Стиль элемента ProgressBar","Progress Bar element style"},
   {"Текущее начение элемента ProgressBar в диапазоне от Min до Max","Current value of the ProgressBar in the range from Min to Max"},
   {"Скорость анимации полосы прогресса при стиле Marquee","Marquee style progress bar animation speed"},
   
//--- Строковые свойства графических элементов
   {"Имя объекта-графического элемента","The name of the graphic element object"},
   {"Имя графического ресурса","Image resource name"},
   {"Текст графического элемента","Text of the graphic element"},
   {"Описание графического элемента","Description of the graphic element"},
   {"Заголовок подсказки элемента","Element tooltip header"},
   {"Текст подсказки элемента","Element tooltip title"},
  };
//+---------------------------------------------------------------------+


При создании коллекций объектов мы используем класс списка, производного от класса динамического массива указателей на экземпляры класса CObject и его наследников. В классе CArrayObj есть метод Detach(), извлекающий указатель на объект из списка и возвращающий полученный указатель. Нам не всегда нужно при извлечении указателя из списка далее его использовать.
Поэтому в производном классе в файле
\MQL5\Include\DoEasy\Collections\ListObj.mqh создадим метод, изымающий указатель из списка без его возврата:

//+------------------------------------------------------------------+
//|                                                      ListObj.mqh |
//|                        Copyright 2019, MetaQuotes Software Corp. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2019, MetaQuotes Software Corp."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include <Arrays\ArrayObj.mqh>
//+------------------------------------------------------------------+
//| Класс списков коллекций                                          |
//+------------------------------------------------------------------+
class CListObj : public CArrayObj
  {
private:
   int               m_type;                    // Тип списка
public:
   bool              DetachElement(const int index)
                       {
                        CObject *obj=CArrayObj::Detach(index);
                        if(obj==NULL)
                           return false;
                        obj=NULL;
                        return true;
                       }
   void              Type(const int type)       { this.m_type=type;     }
   virtual int       Type(void)           const { return(this.m_type);  }
                     CListObj()                 { this.m_type=0x7778;   }
  };
//+------------------------------------------------------------------+

Тут просто: извлекаем указатель из списка и, если это сделать не удалось, возвращаем false.
При удачном извлечении обнуляем полученный указатель и возвращаем true.


Немного доработаем класс объекта-паузы в файле \MQL5\Include\DoEasy\Services\Pause.mqh.

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

//--- Возвращает (1) прошедшее время от начала отсчёта в миллисекундах, (2) флаг завершения ожидания
//--- (3) время начала отсчёта паузы, (4) установленный размер паузы в миллисекундах, (5) начало отсчёта
   ulong             Passed(void)                     const { return this.TickCount()-this.m_start;                     }
   bool              IsCompleted(void)                const { return this.Passed()>this.m_wait_msc;                     }
   ulong             TimeBegin(void)                  const { return this.m_time_begin;                                 }
   ulong             TimeWait(void)                   const { return this.m_wait_msc;                                   }
   ulong             CountdownStart(void)             const { return this.m_start;                                      }

Метод нам потребуется при организации работы с элементами управления в таймере.


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

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

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

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

      int            visible_area_h;                           // Высота области видимости
      bool           displayed;                                // Флаг отображения не скрытого элемента управления
      int            display_state;                            // Состояние отображения элемента управления
      long           display_duration;                         // Продолжительность процесса отображения элемента управления
      int            split_container_fixed_panel;              // Панель, сохраняющая свои размеры при изменении размера контейнера
      bool           split_container_splitter_fixed;           // Флаг перемещаемости разделителя

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

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

      int            border_right_area_width;                  // Ширина области правой грани
      int            border_top_area_width;                    // Ширина области верхней грани
      //--- Вещественные свойства объекта

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

Структура объекта нам нужна для правильного сохранения объекта в файл и восстановления его из файла.


В блоке методов упрощённого доступа к свойствам объекта напишем методы для работы с этими новыми свойствами:

//--- (1) Устанавливает, (2) возвращает флаг отображения не скрытого элемента управления
   virtual void      SetDisplayed(const bool flag)             { this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,flag);                   }
   bool              Displayed(void)                     const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_DISPLAYED);           }
   
//--- (1) Устанавливает, (2) возвращает состояние отображения элемента управления
   void              SetDisplayState(const ENUM_CANV_ELEMENT_DISPLAY_STATE state)
                       { this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE,state);                                                      }
   ENUM_CANV_ELEMENT_DISPLAY_STATE DisplayState(void)    const
                       { return (ENUM_CANV_ELEMENT_DISPLAY_STATE)this.GetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE);                    }
   
//--- (1) Устанавливает, (2) возвращает продолжительность процесса отображения элемента управления
   void              SetDisplayDuration(const long value)      { this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION,value);           }
   long              DisplayDuration(void)               const { return this.GetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION);          }
   
//--- (1) Устанавливает, (2) возвращает тип графического элемента
   void              SetTypeElement(const ENUM_GRAPH_ELEMENT_TYPE type)
                       {
                        CGBaseObj::SetTypeElement(type);
                        this.SetProperty(CANV_ELEMENT_PROP_TYPE,type);
                       }
   ENUM_GRAPH_ELEMENT_TYPE TypeGraphElement(void)  const { return (ENUM_GRAPH_ELEMENT_TYPE)this.GetProperty(CANV_ELEMENT_PROP_TYPE);   }

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

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

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

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

      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Высота области видимости
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true);                        // Флаг отображения не скрытого элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE,CANV_ELEMENT_DISPLAY_STATE_NORMAL);// Состояние отображения элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION,DEF_CONTROL_PROCESS_DURATION);  // Продолжительность процесса отображения элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);                      // X-координата области управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);                      // Y-координата области управления

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

      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,0);                 // Высота области управления
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,0);                 // X-координата области прокрутки справа

      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_TEXT,"");                                            // Текст Tooltip для элемента
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+
//| Защищённый конструктор                                           |
//+------------------------------------------------------------------+
CGCnvElement::CGCnvElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                           CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long    chart_id,
                           const int     wnd_num,
                           const string  descript,
                           const int     x,
                           const int     y,
                           const int     w,
                           const int     h) : m_shadow(false)
  {
   this.m_type=OBJECT_DE_TYPE_GELEMENT; 
   this.m_element_main=main_obj;
   this.m_element_base=base_obj;
   this.m_chart_color_bg=(color)::ChartGetInteger((chart_id==NULL ? ::ChartID() : chart_id),CHART_COLOR_BACKGROUND);
   this.m_name=this.CreateNameGraphElement(element_type);
   this.m_chart_id=(chart_id==NULL || chart_id==0 ? ::ChartID() : chart_id);
   this.m_subwindow=wnd_num;
   this.m_type_element=element_type;
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.m_text_anchor=0;
   this.m_text_x=0;
   this.m_text_y=0;
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetOpacity(0);
   this.m_shift_coord_x=0;
   this.m_shift_coord_y=0;
   if(::ArrayResize(this.m_array_colors_bg,1)==1)
      this.m_array_colors_bg[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_dwn,1)==1)
      this.m_array_colors_bg_dwn[0]=this.BackgroundColor();
   if(::ArrayResize(this.m_array_colors_bg_ovr,1)==1)
      this.m_array_colors_bg_ovr[0]=this.BackgroundColor();
   if(this.Create(chart_id,wnd_num,x,y,w,h,false))
     {
      this.SetProperty(CANV_ELEMENT_PROP_NAME_RES,this.m_canvas.ResourceName()); // Имя графического ресурса
      this.SetProperty(CANV_ELEMENT_PROP_CHART_ID,CGBaseObj::ChartID());         // Идентификатор графика

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

      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Высота области видимости
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,true);                        // Флаг отображения не скрытого элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE,CANV_ELEMENT_DISPLAY_STATE_NORMAL);// Состояние отображения элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION,DEF_CONTROL_PROCESS_DURATION);  // Продолжительность процесса отображения элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_X,0);                      // X-координата области управления
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_Y,0);                      // Y-координата области управления

      //---...
      //---...
 
      this.SetProperty(CANV_ELEMENT_PROP_CONTROL_AREA_HEIGHT,0);                 // Высота области управления
      this.SetProperty(CANV_ELEMENT_PROP_SCROLL_AREA_X_RIGHT,0);                 // X-координата области прокрутки справа

      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_TITLE,"");                                           // Заголовок Tooltip для элемента
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_TEXT,"");                                            // Текст Tooltip для элемента
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+


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

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

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

   this.m_struct_obj.visible_area_h=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT);  // Высота области видимости
   this.m_struct_obj.displayed=(bool)this.GetProperty(CANV_ELEMENT_PROP_DISPLAYED);                // Флаг отображения не скрытого элемента управления
   this.m_struct_obj.display_state=(int)this.GetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE);         // Состояние отображения элемента управления
   this.m_struct_obj.display_duration=this.GetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION);        // Продолжительность процесса отображения элемента управления
   this.m_struct_obj.zorder=this.GetProperty(CANV_ELEMENT_PROP_ZORDER);                            // Приоритет графического объекта на получение события нажатия мышки на графике
   this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED);                    // Флаг доступности элемента

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

   this.m_struct_obj.fore_color=(color)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR);             // Цвет текста по умолчанию для всех объектов элемента управления
   this.m_struct_obj.fore_color_opacity=(uchar)this.GetProperty(CANV_ELEMENT_PROP_FORE_COLOR_OPACITY); // Непрозрачность цвета текста по умолчанию для всех объектов элемента управления

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

   this.m_struct_obj.border_right_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH);    // Ширина области правой грани
   this.m_struct_obj.border_top_area_width=(int)this.GetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH);        // Ширина области верхней грани
//--- Сохранение вещественных свойств

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


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

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

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

   this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,this.m_struct_obj.visible_area_h);       // Высота области видимости
   this.SetProperty(CANV_ELEMENT_PROP_DISPLAYED,this.m_struct_obj.displayed);                      // Флаг отображения не скрытого элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_STATE,this.m_struct_obj.display_state);              // Состояние отображения элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_DISPLAY_DURATION,this.m_struct_obj.display_duration);        // Продолжительность процесса отображения элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_ZORDER,this.m_struct_obj.zorder);                            // Приоритет графического объекта на получение события нажатия мышки на графике
   this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled);                          // Флаг доступности элемента

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

   this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_OPACITY,this.m_struct_obj.background_color_opacity);       // Непрозрачность элемента
   this.SetProperty(CANV_ELEMENT_PROP_BACKGROUND_COLOR_MOUSE_DOWN,this.m_struct_obj.background_color_mouse_down); // Цвет фона элемента управления при нажатии мышки на элемент управления
   
   //---...
   //---...

   this.SetProperty(CANV_ELEMENT_PROP_BORDER_BOTTOM_AREA_WIDTH,this.m_struct_obj.border_bottom_area_width);    // Ширина области нижней грани
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_RIGHT_AREA_WIDTH,this.m_struct_obj.border_right_area_width);      // Ширина области правой грани
   this.SetProperty(CANV_ELEMENT_PROP_BORDER_TOP_AREA_WIDTH,this.m_struct_obj.border_top_area_width);          // Ширина области верхней грани
//--- Сохранение вещественных свойств

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


В файле \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_GLARE_OBJ                 ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_GLARE_OBJ)                :
      type==GRAPH_ELEMENT_TYPE_FORM                      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)                     :
      type==GRAPH_ELEMENT_TYPE_WINDOW                    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)                   :
      //--- WinForms
      type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)              :
      type==GRAPH_ELEMENT_TYPE_WF_BASE                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)                  :
      //--- Контейнеры
      type==GRAPH_ELEMENT_TYPE_WF_CONTAINER              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER)             :
      type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_PANEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)           :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)       :
      //--- Стандартные элементы управления
      type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE)           :
      type==GRAPH_ELEMENT_TYPE_WF_LABEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON)           :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON                 ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON)                :
      type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX)     :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM)         :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX)      :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX)       :
      type==GRAPH_ELEMENT_TYPE_WF_TOOLTIP                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP)               :
      type==GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR)          :
      //--- Вспомогательные объекты элементов управления
      type==GRAPH_ELEMENT_TYPE_WF_TAB_HEADER             ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_HEADER)            :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_FIELD              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD)             :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON)          :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP)       :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT)     :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT     ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT)    :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX)  :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL) :
      type==GRAPH_ELEMENT_TYPE_WF_SPLITTER               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLITTER)              :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_BASE              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE)             :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT)        :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT)       :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP           ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP)          :
      type==GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN)        :
      type==GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR)      :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+

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


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

В  файле \MQL5\Include\DoEasy\Objects\Graph\Form.mqh, в его публичной секции напишем виртуальный обработчик событий таймера:

//--- Обработчик событий
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Обработчик событий мышки
   virtual void      OnMouseEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Обработчик последнего события мышки
   virtual void      OnMouseEventPostProcessing(void);

//--- Таймер
   virtual void      OnTimer(void)  { return; }

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


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

//--- Добавляет новый присоединённый элемент
   bool              AddNewElement(CGCnvElement *obj,const int x,const int y);

//--- (1) Присоединяет объект-ToolTip к элементу
   bool              AddTooltip(CForm *tooltip);
//--- Возвращает (1) количество объектов ToolTip, (2) присоединённый объект-ToolTip, (3) по описанию
   int               ToolTipTotal(void);
   CForm            *GetTooltip(void);
   CForm            *GetTooltipByDescription(const string descript);
//--- Устанавливает текст для Tooltip
   virtual void      SetTooltipText(const string text);

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

Доработаем метод, назначающий объекту указанный объект-ToolTip.

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

//+------------------------------------------------------------------+
//| Назначает объекту указанный объект-ToolTip                       |
//+------------------------------------------------------------------+
bool CForm::AddTooltip(CForm *tooltip)
  {
//--- Если передан указатель на пустой объект или тип объекта не равен Tooltip - сообщаем об ошибке и возвращаем false
   if(tooltip==NULL)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT);
      return false;
     }
//--- Если передан указатель на объект, тип которого не равен Tooltip - сообщаем об ошибке и возвращаем false
   if(tooltip.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_ELEMENT_TYPE_WF_WRONG_TYPE_PASSED);
      return false;
     }
//--- Если в списке прикреплённых объектов уже есть объект-Tooltip с таким же описанием, как и у переданного в метод объекта - 
//--- сообщаем об этом в журнал и возвращаем false
   if(this.GetTooltipByDescription(tooltip.Description())!=NULL)
     {
      ::Print(DFUN,this.TypeElementDescription()+": ",CMessage::Text(MSG_FORM_TOOLTIP_OBJ_ALREADY_EXISTS),": ",tooltip.Name(),", Description: \"",tooltip.Description(),"\"");
      return false;
     }
//--- Если в списке прикреплённых объектов уже есть объект-Tooltip - сообщаем об этом в журнал и возвращаем false
   if(this.ToolTipTotal()>0)
     {
      ::Print(DFUN,CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CANT_ADD_2_TOOLTIP));
      return false;
     }
//--- Если не удалось добавить объект-Tooltip в список прикреплённых объектов - сообщаем об ошибке и возвращаем false
   if(!this.m_list_elements.Add(tooltip))
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST),": ",tooltip.NameObj());
      return false;
     }
//--- Если координаты добавленного в список объекта изменены - устанавливаем относительные координаты Tooltip
   if(tooltip.Move(this.CoordX(),this.CoordY()))
     {
      tooltip.SetCoordXRelative(tooltip.CoordX()-this.CoordX());
      tooltip.SetCoordYRelative(tooltip.CoordY()-this.CoordY());
     }
//--- Устанавливаем этот объект как базовый для объекта Tooltip и возвращаем true
   tooltip.SetBase(this.GetObject());
   return true;
  }
//+------------------------------------------------------------------+

Если мы по ошибке попытаемся присоединить вместо объекта-подсказки объект другого типа, то метод сообщит об ошибке. Если же к объекту уже прикреплён ToolTip, то тоже будет выведено сообщение об ошибке и метод вернёт false.


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

//+------------------------------------------------------------------+
//| Возвращает количество объектов ToolTip                           |
//+------------------------------------------------------------------+
int CForm::ToolTipTotal(void)
  {
   int res=0;
   for(int i=this.ElementsTotal()-1;i>WRONG_VALUE;i--)
     {
      CGCnvElement *obj=this.GetElement(i);
      if(obj!=NULL && obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
         res++;
     }
   return res;
  }
//+------------------------------------------------------------------+

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


Таймер коллекции графических элементов обновляется раз в 16 миллисекунд. Чтобы сделать появление или затухание всплывающей подсказки в течении, например, одной секунды, нам нужно 1000 миллисекунд разделить на период обновления таймера. В итоге получим, что изменение непрозрачности объекта нам нужно производить примерно каждые 1000/16=62.5 миллисекунды.

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\ToolTip.mqh объявим переменную для хранения шага изменения прозрачности:

//+------------------------------------------------------------------+
//| Класс базового объекта Hint элементов управления WForms          |
//+------------------------------------------------------------------+
class CToolTip : public CHintBase
  {
private:
   int               m_step;                             // Шаг изменения прозрачности
//--- Корректирует размеры подсказки по размерам текстов
   void              CorrectSizeByTexts(void);
protected:


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

//--- Показывает элемент
   virtual void      Show(void);
//--- Перерисовывает объект
   virtual void      Redraw(bool redraw);
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Очищает элемент заливкой градиентом
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Рисует рамку подсказки
   virtual void      DrawFrame(void);
//--- Инициализирует переменные
   virtual void      Initialize(void);
//--- Таймер
   virtual void      OnTimer(void);
  };
//+------------------------------------------------------------------+


А в каждом из конструкторов рассчитаем значение переменной, хранящей шаг изменения прозрачности, исходя из значений по умолчанию для продолжительности появления/затухания и шага счётчика таймера коллекции графических элементов:

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CToolTip::CToolTip(const ENUM_GRAPH_ELEMENT_TYPE type,
                   CGCnvElement *main_obj,CGCnvElement *base_obj,
                   const long chart_id,
                   const int subwindow,
                   const string descript,
                   const int x,
                   const int y,
                   const int w,
                   const int h) : CHintBase(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.Initialize();
   this.m_step=(int)::floor(DEF_CONTROL_PROCESS_DURATION/COLLECTION_GRAPH_ELM_COUNTER_STEP);
  }
//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CToolTip::CToolTip(CGCnvElement *main_obj,CGCnvElement *base_obj,
                   const long chart_id,
                   const int subwindow,
                   const string descript,
                   const int x,
                   const int y,
                   const int w,
                   const int h) : CHintBase(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TOOLTIP);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.Initialize();
   this.m_step=(int)::floor(DEF_CONTROL_PROCESS_DURATION/COLLECTION_GRAPH_ELM_COUNTER_STEP);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Инициализирует переменные                                        |
//+------------------------------------------------------------------+
void CToolTip::Initialize(void)
  {
   this.SetBackgroundColor(CLR_DEF_CONTROL_HINT_BACK_COLOR,true);
   this.SetBorderColor(CLR_DEF_CONTROL_HINT_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_CONTROL_HINT_FORE_COLOR,true);
   this.SetDisplayed(false);
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetShadow(true);
   this.DrawShadow(2,2,CLR_DEF_SHADOW_COLOR,CLR_DEF_SHADOW_OPACITY,DEF_SHADOW_BLUR);
   this.SetOpacity(255,false);
   this.SetTitle("");
   this.SetTooltipText("");
   this.SetInitialDelay(DEF_CONTROL_TOOLTIP_INITIAL_DELAY);
   this.SetAutoPopDelay(DEF_CONTROL_TOOLTIP_AUTO_POP_DELAY);
   this.SetReshowDelay(DEF_CONTROL_TOOLTIP_RESHOW_DELAY);
   this.SetShowAlways(false);
   this.SetIcon(CANV_ELEMENT_TOOLTIP_ICON_NONE);
   this.SetBalloon(false);
   this.SetUseFading(true);
   this.SetTextAlign(ANCHOR_LEFT_UPPER);
   this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP);
   this.SetFont(DEF_FONT,DEF_FONT_SIZE,FW_NORMAL);
   this.SetDisplayed(false);
   this.Hide();
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Показывает элемент                                               |
//+------------------------------------------------------------------+
void CToolTip::Show(void)
  {
//--- Если элемент не должен показываться (скрыт внутри другого элемента управления) - уходим
   if(!this.Displayed() || this.TooltipText()=="")
      return;
//--- Отобразим объект
   CGCnvElement::Show();
//--- Получим объект "Тень"
   CShadowObj *shadow=this.GetShadowObj();
//--- Если у объекта есть тень, и объект "Тень" существует - отобразим тень
   if(this.IsShadow() && shadow!=NULL)
     {
      shadow.Show();
      this.BringToTop();
     }
//--- Перерисуем объект
   this.Redraw(true);
  }
//+------------------------------------------------------------------+

Теперь метод выглядит так:

//+------------------------------------------------------------------+
//| Показывает элемент                                               |
//+------------------------------------------------------------------+
void CToolTip::Show(void)
  {
//--- Если элемент не должен показываться (скрыт внутри другого элемента управления) - уходим
   if(!this.Displayed() || this.TooltipText()=="")
      return;
//--- Получим объект "Тень"
   CShadowObj *shadow=this.GetShadowObj();
//--- Если у объекта есть тень, и объект "Тень" существует - отобразим тень
   if(this.IsShadow() && shadow!=NULL)
     {
      shadow.Show();
      this.BringToTop();
     }
//--- Отобразим объект
   CGCnvElement::Show();
  }
//+------------------------------------------------------------------+

Такая доработка немного уменьшает неприятное "моргание" элемента при постепенном изменении его непрозрачности.


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

//+------------------------------------------------------------------+
//| Рисует подсказку                                                 |
//+------------------------------------------------------------------+
void CToolTip::DrawHint(const int shift)
  {
   int y=3;
   int x=6+(this.Icon()>CANV_ELEMENT_TOOLTIP_ICON_NONE ? 16 : 0);
   this.DrawIcon();
   if(this.Title()!="" && this.Title()!=NULL)
     {
      this.SetFont(DEF_FONT,DEF_FONT_SIZE,FW_BLACK);
      this.Text(x,y,this.Title(),this.ForeColor(),this.Opacity(),this.TextAnchor());
      this.SetFont(DEF_FONT,DEF_FONT_SIZE,FW_NORMAL);
      y+=this.TextHeight(this.Title())+4;
     }
   this.Text(x,y,this.Text(),this.ForeColor(),this.Opacity(),this.TextAnchor());
   this.Update();
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void CToolTip::OnTimer(void)
  {
//--- Если объект находится в обычном состоянии (скрыт)
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_NORMAL)
     {
      //--- задаём объекту состояние ожидания плавного появления,
      //--- задаём длительность ожидания и устанавливаем время отсчёта
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN);
      this.m_pause.SetWaitingMSC(this.InitialDelay());
      this.m_pause.SetTimeBegin();
      return;
     }
//--- Если объект находится в состоянии ожидания плавного появления
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_IN)
     {
      //--- Если ещё не прошло время ожидания - уходим
      if(this.m_pause.Passed()<this.InitialDelay())
         return;
      //--- Устанавливаем состояние нахождения объекта в процессе плавного появления и
      //---  устанавливаем время отсчёта начала процесса
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_IN);
      this.m_pause.SetTimeBegin();
      return;
     }
//--- Если объект находится в состоянии плавного появления
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_IN)
     {
      //--- Если объект ещё не полностью непрозрачен
      if(this.Opacity()<255)
        {
         //--- устанавливаем флаг отображения и показываем объект,
         //--- рассчитываем необходимое значение непрозрачности на этом шаге таймера,
         //--- устанавливаем в свойства рассчитанную непрозрачность и перерисовываем объект
         this.SetDisplayed(true);
         if(this.Opacity()==0)
            this.Show();
         uchar value=(this.Opacity()+(uchar)this.m_step<255 ? this.Opacity()+(uchar)this.m_step : 255);
         this.SetOpacity(value);
         this.Redraw(true);
         return;
        }
      //--- Устанавливаем состояние завершения плавного появления
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_IN);
     }
//--- Если объект находится в состоянии завершения плавного появления
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_IN)
     {
      //--- задаём объекту состояние ожидания плавного затухания,
      //--- задаём длительность ожидания и устанавливаем время отсчёта
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_OUT);
      this.m_pause.SetWaitingMSC(this.AutoPopDelay());
      this.m_pause.SetTimeBegin();
      return;
     }
//--- Если объект находится в состоянии ожидания плавного затухания
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_WAITING_FADE_OUT)
     {
      //--- Если ещё не прошло время ожидания - уходим
      if(this.m_pause.Passed()<this.AutoPopDelay())
         return;
      //--- Устанавливаем состояние нахождения объекта в процессе плавного затухания и
      //---  устанавливаем время отсчёта начала процесса
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_OUT);
      this.m_pause.SetTimeBegin();
      return;
     }
//--- Если объект находится в состоянии плавного затухания
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_PROCESS_FADE_OUT)
     {
      //--- Если объект ещё не полностью прозрачен
      if(this.Opacity()>0)
        {
         //--- устанавливаем флаг отображения,
         //--- рассчитываем необходимое значение непрозрачности на этом шаге таймера,
         //--- устанавливаем в свойства рассчитанную непрозрачность и перерисовываем объект
         //this.SetDisplayed(true);
         uchar value=(this.Opacity()-(uchar)this.m_step>0 ? this.Opacity()-(uchar)this.m_step : 0);
         this.SetOpacity(value);
         this.Redraw(true);
         return;
        }
      //--- Устанавливаем состояние завершения плавного затухания,
      //--- устанавливаем в свойства полную прозрачность и перерисовываем объект
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_OUT);
      this.SetOpacity(0);
      this.Redraw(true);
     }
//--- Если объект находится в состоянии завершения плавного затухания
   if(this.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_COMPLETED_FADE_OUT)
     {
      //--- Устанавливаем флаг неотображения, устанавливаем полную прозрачность,
      //--- скрываем объект и устанавливаем состояние завершения всего цикла изменений
      this.SetDisplayed(false);
      this.SetOpacity(0);
      this.Hide();
      this.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_COMPLETED);
     }
  }
//+------------------------------------------------------------------+

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

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

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

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


Постепенно мы приближаемся к созданию нового WinForms-объекта ProgressBar. Для этого элемента управления нам нужно будет создать вспомогательный объект "Блик". Объекты этого типа будут служить для визуального оформления некоторых элементов GUI, и в объекте ProgressBar блик должен пробегать по линии прогресса. Концепция будет такой: нарисуем белое пятно необходимой формы и размера и размоем его от центра к краям. Объект-блик будет таким же полупрозрачным как и объект-тень. И вот тут мы подходим к тому, что такой объект нам нужно унаследовать от объекта-тени — чтобы воспользоваться его методами размытия, а прорисовку сделать собственной. Чтобы нормально наследоваться от объекта-тени, нам в него нужно добавить защищённый конструктор, в котором будем указывать тип создаваемого объекта.

Внесём изменения в класс объекта-тени в файле \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh.

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

//+------------------------------------------------------------------+
//| Класс объекта Тени                                               |
//+------------------------------------------------------------------+
class CShadowObj : public CGCnvElement
  {
private:
   color             m_color;                         // Цвет тени
   uchar             m_opacity;                       // Непрозрачность тени
   uchar             m_blur;                          // Размытие
//--- Рисует форму тени объекта
   void              DrawShadowFigureRect(const int w,const int h);
protected:
//--- Размытие по-Гауссу
   bool              GaussianBlur(const uint radius);
//--- Возвращает массив весовых коэффициентов
   bool              GetQuadratureWeights(const double mu0,const int n,double &weights[]);
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CShadowObj(const ENUM_GRAPH_ELEMENT_TYPE type,
                                CGCnvElement *main_obj,CGCnvElement *base_obj,
                                const long chart_id,
                                const int subwindow,
                                const string descript,
                                const int x,
                                const int y,
                                const int w,
                                const int h);
public:
//--- Конструктор с указанием главного и базового объектов, идентификатора чарта и подокна
                     CShadowObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                const long chart_id,
                                const int subwindow,
                                const string descript,
                                const int x,
                                const int y,
                                const int w,
                                const int h);

//--- Поддерживаемые свойства объекта (1) целочисленные, (2) вещественные, (3) строковые
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Рисует тень объекта

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


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

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(const ENUM_GRAPH_ELEMENT_TYPE type,
                       CGCnvElement *main_obj,CGCnvElement *base_obj,
                       const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CGCnvElement(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GSHADOW; 
   CGCnvElement::SetBackgroundColor(clrNONE,true);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.SetOpacityDraw(CLR_DEF_SHADOW_OPACITY);
   this.SetBlur(DEF_SHADOW_BLUR);
   color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-100);
   this.m_color=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.SetVisibleFlag(false,false);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                       const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_SHADOW_OBJ,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GSHADOW; 
   CGCnvElement::SetBackgroundColor(clrNONE,true);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.SetOpacityDraw(CLR_DEF_SHADOW_OPACITY);
   this.SetBlur(DEF_SHADOW_BLUR);
   color gray=CGCnvElement::ChangeColorSaturation(this.ChartBackgroundColor(),-100);
   this.m_color=CGCnvElement::ChangeColorLightness(gray,-50);
   this.m_shadow=false;
   this.SetVisibleFlag(false,false);
   CGCnvElement::Erase();
  }
//+------------------------------------------------------------------+

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


Элемент управления ProgressBar

Сначала создадим вспомогательный объект-блик в папке \MQL5\Include\DoEasy\Objects\Graph\WForms\ в файле GlareObj.mqh. Так как сегодня нам этот объект не нужен будет, то и рассматривать его не будем — он сделан полностью по образу и подобию класса объекта-тени. Лишь цвет рисуемого блика установлен как белый:

//+------------------------------------------------------------------+
//|                                                     GlareObj.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 "..\ShadowObj.mqh"
//+------------------------------------------------------------------+
//| Класс объекта блика                                              |
//+------------------------------------------------------------------+
class CGlareObj : public CShadowObj
  {
private:
   color             m_color;                         // Цвет блика
   uchar             m_opacity;                       // Непрозрачность блика
   uchar             m_blur;                          // Размытие
//--- Рисует форму блика объекта
   void              DrawGlareFigure(const int w,const int h);
   void              DrawGlareFigureRect(const int w,const int h);
protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CGlareObj(const ENUM_GRAPH_ELEMENT_TYPE type,
                               CGCnvElement *main_obj,CGCnvElement *base_obj,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
public:
//--- Конструктор с указанием главного и базового объектов, идентификатора чарта и подокна
                     CGlareObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);

//--- Поддерживаемые свойства объекта (1) целочисленные, (2) вещественные, (3) строковые
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Рисует блик объекта
   void              Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw);
  };
//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CGlareObj::CGlareObj(const ENUM_GRAPH_ELEMENT_TYPE type,
                     CGCnvElement *main_obj,CGCnvElement *base_obj,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CShadowObj(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GGLARE; 
   CGCnvElement::SetBackgroundColor(clrNONE,true);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.SetOpacityDraw(CLR_DEF_SHADOW_OPACITY);
   this.SetBlur(DEF_SHADOW_BLUR);
   this.m_color=clrWhite;
   this.m_shadow=false;
   this.SetVisibleFlag(false,false);
  }
//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CGlareObj::CGlareObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CShadowObj(GRAPH_ELEMENT_TYPE_GLARE_OBJ,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GGLARE; 
   CGCnvElement::SetBackgroundColor(clrNONE,true);
   CGCnvElement::SetOpacity(0);
   CGCnvElement::SetActive(false);
   this.SetOpacityDraw(CLR_DEF_SHADOW_OPACITY);
   this.SetBlur(DEF_SHADOW_BLUR);
   this.m_color=clrWhite;
   this.m_shadow=false;
   this.SetVisibleFlag(false,false);
  }
//+------------------------------------------------------------------+
//| Рисует блик объекта                                              |
//+------------------------------------------------------------------+
void CGlareObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw)
  {
   if(!this.IsVisible())
      return;
//--- Устанавливаем в переменные значения смещения блика по осям X и Y
   this.SetCoordXRelative(shift_x);
   this.SetCoordYRelative(shift_y);
//--- Рассчитываем ширину и высоту рисуемого прямоугольника
   int w=this.Width()-OUTER_AREA_SIZE*2;
   int h=this.Height()-OUTER_AREA_SIZE*2;
//--- Рисуем закрашенный прямоугольник с рассчитанными размерами
   this.DrawGlareFigure(w,h);
//--- Рассчитываем радиус размытия, который не может быть больше четверти размера константы OUTER_AREA_SIZE
   this.m_blur=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value);
//--- Если размыть фигуру не удалось - уходим из метода (ошибку в журнал выведет GaussianBlur())
   if(!this.GaussianBlur(this.m_blur))
      return;
//--- Смещаем объект блика на указанные в аргументах метода смещения по X и Y и обновляем канвас
   CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),false);
   CGCnvElement::Update(redraw);
  }
//+------------------------------------------------------------------+
//| Рисует форму блика объекта                                       |
//+------------------------------------------------------------------+
void CGlareObj::DrawGlareFigure(const int w,const int h)
  {
   this.DrawGlareFigureRect(w,h);
  }
//+------------------------------------------------------------------+
//| Рисует прямоугольную форму блика объекта                         |
//+------------------------------------------------------------------+
void CGlareObj::DrawGlareFigureRect(const int w,const int h)
  {
   CGCnvElement::DrawRectangleFill(OUTER_AREA_SIZE,OUTER_AREA_SIZE,OUTER_AREA_SIZE+w-1,OUTER_AREA_SIZE+h-1,this.m_color,this.OpacityDraw());
   CGCnvElement::Update();
  }
//+------------------------------------------------------------------+

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


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

В папке библиотеки \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ создадим новый файл BarProgressBar.mqh класса CBarProgressBar. Класс должен быть унаследован от базового объекта всех WinForms-объектов библиотеки, файл которого должен быть подключен к файлу создаваемого класса:

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


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

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "..\WinFormBase.mqh"
//+------------------------------------------------------------------+
//| Класс объекта BarProgressBar элемента управления ProgressBar     |
//+------------------------------------------------------------------+
class CBarProgressBar : public CWinFormBase
  {
private:

protected:
   //--- Выводит блик
   virtual void      DrawGlare(void);
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CBarProgressBar(const ENUM_GRAPH_ELEMENT_TYPE type,
                                     CGCnvElement *main_obj,CGCnvElement *base_obj,
                                     const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
public:
//--- Устанавливает (1) скорость анимации при стиле Marquee, (2) стиль, (3) величину приращения, (4) текущее начение элемента ProgressBar
   void              SetMarqueeAnimationSpeed(const int value)    { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED,value);  }
   void              SetStyle(const ENUM_CANV_ELEMENT_PROGRESS_BAR_STYLE style) { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE,style); }
   void              SetStep(const int value)                     { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STEP,value);                }
   void              SetValue(const int value)                    { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE,value);               }

//--- Поддерживаемые свойства объекта (1) целочисленные, (2) вещественные, (3) строковые
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Конструктор
                     CBarProgressBar(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                     const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
//--- Таймер
   virtual void      OnTimer(void);
  };
//+------------------------------------------------------------------+

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


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

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

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


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

//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CBarProgressBar::CBarProgressBar(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                 const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h) : CWinFormBase(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(0);
   this.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true);
   this.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true);
   this.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Рисует блик                                                      |
//+------------------------------------------------------------------+
void CBarProgressBar::DrawGlare(void)
  {
   
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void CBarProgressBar::OnTimer(void)
  {
   Comment(DFUN,GetTickCount());
  }
//+------------------------------------------------------------------+


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

Концепция будет такой: каждый из графических элементов, для которого планируется его независимая обработка в таймере, будет считаться активным элементом. Для обработки активных элементов создадим список, в который будут помещаться указатели на такие объекты. Этот список всегда будет создаваться в главном объекте и, соответственно, виден в классе-коллекции графических элементов. А в таймере класса-коллекции мы будем проходить в цикле по всем главным объектам-формам, получать указатели на активные элементы и вызывать их обработчики событий таймера. Таким образом мы не будем вынуждены каждый графический элемент искать в главном объекте, а сразу же создавать список таких элементов в главном объекте и обрабатывать только их.

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

//+------------------------------------------------------------------+
//|                                                  WinFormBase.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 "GlareObj.mqh"
#include "..\Form.mqh"
#include "..\..\..\Services\Select.mqh"
//+------------------------------------------------------------------+
//| Класс объекта "форма"                                            |
//+------------------------------------------------------------------+
class CWinFormBase : public CForm
  {
protected:
   CArrayObj        *m_list_active_elements;                   // Указатель на список активных элементов
   color             m_fore_color_init;                        // Первоначальный цвет текста элемента
   color             m_fore_state_on_color_init;               // Первоначальный цвет текста элемента в состоянии "ON"
private:
//--- Возвращает флаги шрифта
   uint              GetFontFlags(void);
public:

Подключение сюда файла объекта-блика даст нам возможность создавать и использовать его во многих графических элементах библиотеки.

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

public:
//--- Рисует рамку
   virtual void      DrawFrame(void){}
//--- Возвращает по типу (1) список, (2) количество привязанных элементов, привязанный элемент (3) по индексу в списке, (4) по имени
   CArrayObj        *GetListElementsByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   int               ElementsTotalByType(const ENUM_GRAPH_ELEMENT_TYPE type);
   CGCnvElement     *GetElementByType(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   CGCnvElement     *GetElementByName(const string name);
//--- Возвращает список активных элементов (1) текущего, (2) главного объекта
   CArrayObj        *GetListActiveElements(void)      { return this.m_list_active_elements; }
   CArrayObj        *GetListMainActiveElements(void);
//--- Возвращает количество активных элементов главного объекта
   int               ListMainActiveElementsTotal(void);
//--- Возвращает элемент из списка активных элементов главного объекта по индексу
   CWinFormBase     *GetActiveElement(const int index);
//--- Возвращает индекс указанного объекта в списке активных элементов главного объекта
   int               IndexActiveElements(CWinFormBase *obj);
//--- Возвращает флаг наличия объекта по имени в списке активных элементов
   bool              IsPresentObjInListActiveElements(string name_obj);
//--- Добавляет (1) указанный, (2) текущий объект в список активных элементов
   bool              AddObjToListActiveElements(CWinFormBase *obj);
   bool              AddObjToListActiveElements(void);
//--- Изымает (1) указанный, (2) текущий объект из списка активных элементов
   bool              DetachObjFromListActiveElements(CWinFormBase *obj);
   bool              DetachObjFromListActiveElements(void);
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);


Добавим деструктор класса:

public:
//--- Конструктор
                     CWinFormBase(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
//--- Деструктор
                    ~CWinFormBase(void)
                      {
                       if(this.m_list_active_elements!=NULL)
                         {
                           this.m_list_active_elements.Clear();
                           delete this.m_list_active_elements;
                         }
                      }
//--- (1) Устанавливает, (2) возвращает цвет текста по умолчанию всех объектов на панели

Если список активных элементов создан — очищаем список и удаляем созданный объект списка.


В конструкторах класса пропишем создание объекта-списка активных элементов:

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                           CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   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;
   this.m_list_active_elements=new CArrayObj();
  }
//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CWinFormBase::CWinFormBase(CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CForm(GRAPH_ELEMENT_TYPE_WF_BASE,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим тип графического элемента и тип объекта библиотеки как базовый WinForms-объект
   this.SetTypeElement(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;
   this.m_list_active_elements=new CArrayObj();
  }
//+------------------------------------------------------------------+


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

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

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

      property==CANV_ELEMENT_PROP_DISPLAYED                    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_DISPLAYED)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_DISPLAY_STATE                ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_DISPLAY_STATE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_DISPLAY_DURATION             ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_DISPLAY_DURATION)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_GROUP                        ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :

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


      property==CANV_ELEMENT_PROP_TOOLTIP_USE_FADING           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_USE_FADING)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
         
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_STEP            ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_STEP)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+


Метод, возвращающий список активных элементов главного объекта:

//+------------------------------------------------------------------+
//| Возвращает список активных элементов главного объекта            |
//+------------------------------------------------------------------+
CArrayObj *CWinFormBase::GetListMainActiveElements(void)
  {
   CWinFormBase *main=this.GetMain();
   if(main==NULL)
      main=this.GetObject();
   CArrayObj *list=main.GetListActiveElements();
   if(list==NULL)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_OBJ_FAILED_GET_ACTIVE_OBJ_LIST);
      return NULL;
     }
   return list;
  }
//+------------------------------------------------------------------+

Здесь: получаем указатель на главный объект. Если возвращён NULL, значит этот объект и является главным. Получаем из главного объекта список активных элементов. Если список получить не удалось — сообщаем об этом и возвращаем NULL. Иначе — возвращаем указатель на список.


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

//+------------------------------------------------------------------+
//| Возвращает количество активных элементов главного объекта        |
//+------------------------------------------------------------------+
int CWinFormBase::ListMainActiveElementsTotal(void)
  {
   return(this.GetListMainActiveElements()!=NULL ? this.GetListMainActiveElements().Total() : 0);
  }
//+------------------------------------------------------------------+

Если указатель на список главного объекта удалось получитьвозвращаем количество элементов списка, иначе — ноль.


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

//+------------------------------------------------------------------+
//| Возвращает индекс указанного объекта                             |
//| в списке активных элементов главного объекта                     |
//+------------------------------------------------------------------+
int CWinFormBase::IndexActiveElements(CWinFormBase *obj)
  {
   CArrayObj *list=this.GetListMainActiveElements();
   if(list==NULL)
      return WRONG_VALUE;
   for(int i=0;i<this.ListMainActiveElementsTotal();i++)
     {
      CWinFormBase *elm=list.At(i);
      if(elm!=NULL && elm.Name()==obj.Name())
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Возвращает элемент из списка активных                            |
//| элементов главного объекта по индексу                            |
//+------------------------------------------------------------------+
CWinFormBase *CWinFormBase::GetActiveElement(const int index)
  {
   CArrayObj *list=this.GetListMainActiveElements();
   return(list!=NULL ? list.At(index) : NULL);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Возвращает флаг наличия объекта по имени                         |
//| в списке активных элементов главного объекта                     |
//+------------------------------------------------------------------+
bool CWinFormBase::IsPresentObjInListActiveElements(string name_obj)
  {
   CArrayObj *list=this.GetListMainActiveElements();
   if(list==NULL)
      return false;
   for(int i=list.Total()-1;i>WRONG_VALUE;i--)
     {
      CWinFormBase *obj=list.At(i);
      if(obj!=NULL && obj.Name()==name_obj)
         return true;
     }
   return false;
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Добавляет указанный объект в список                              |
//| активных элементов главного объекта                              |
//+------------------------------------------------------------------+
bool CWinFormBase::AddObjToListActiveElements(CWinFormBase *obj)
  {
   CArrayObj *list=this.GetListMainActiveElements();
   if(list==NULL)
      return false;
   if(obj==NULL || this.IsPresentObjInListActiveElements(obj.Name()))
      return false;
   return list.Add(obj);
  }
//+------------------------------------------------------------------+

Получаем список активных элементов главного объекта. Если объект с таким именем уже есть в списке — возвращаем false. Иначе — возвращаем результат добавления объекта в список.


Метод, добавляющий текущий объект в список активных элементов главного объекта:

//+------------------------------------------------------------------+
//| Добавляет текущий объект в список                                |
//| активных элементов главного объекта                              |
//+------------------------------------------------------------------+
bool CWinFormBase::AddObjToListActiveElements(void)
  {
   return this.AddObjToListActiveElements(this.GetObject());
  }
//+------------------------------------------------------------------+

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


Метод, изымающий указанный объект из списка активных элементов главного объекта:

//+------------------------------------------------------------------+
//| Изымает указанный объект из списка                               |
//| активных элементов главного объекта                             |
//+------------------------------------------------------------------+
bool CWinFormBase::DetachObjFromListActiveElements(CWinFormBase *obj)
  {
   CArrayObj *list=this.GetListMainActiveElements();
   if(list==NULL)
      return false;
   int index=this.IndexActiveElements(obj);
   if(index==WRONG_VALUE)
      return false;
   CWinFormBase *elm=list.Detach(index);
   if(elm==NULL)
      return false;
   elm=NULL;
   return true;
  }
//+------------------------------------------------------------------+

Получаем указатель на список активных элементов главного объекта. Получаем индекс объекта в списке, указатель на который передан в метод. Получаем указатель на изъятый из списка объект. Если объект изъять не удалось — возвращаем false. Иначе — обнуляем указатель и возвращаем true.


Метод, изымающий текущий объект из списка активных элементов главного объекта:

//+------------------------------------------------------------------+
//| Изымает текущий объект из списка                                 |
//| активных элементов главного объекта                              |
//+------------------------------------------------------------------+
bool CWinFormBase::DetachObjFromListActiveElements(void)
  {
   return this.DetachObjFromListActiveElements(this.GetObject());
  }
//+------------------------------------------------------------------+

Возвращаем результат вызова вышерассмотренного метода. В качестве изымаемого объекта указываем текущий.


Создадим элемент управления ProgressBar.

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

//+------------------------------------------------------------------+
//|                                                  ProgressBar.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 "..\Helpers\BarProgressBar.mqh"
//+------------------------------------------------------------------+
//| Класс объекта ArrowLeftRightBox элементов управления WForms      |
//+------------------------------------------------------------------+
class CProgressBar : public CContainer
  {
  }


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

//+------------------------------------------------------------------+
//| Класс объекта ArrowLeftRightBox элементов управления WForms      |
//+------------------------------------------------------------------+
class CProgressBar : public CContainer
  {
private:
//--- Создаёт новый графический объект
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
//--- Создаёт объект-полосу прогресса
   void              CreateProgressBar(void);

//--- Инициализирует свойства элемента
   void              Initialize(void);

protected:


В защищённой секции класса объявим защищённый конструктор:

protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CProgressBar(const ENUM_GRAPH_ELEMENT_TYPE type,
                                  CGCnvElement *main_obj,CGCnvElement *base_obj,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
public:


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

public:
//--- (1) Устанавливает, (2) возвращает скорость анимации полосы прогресса при стиле Marquee
   void              SetMarqueeAnimationSpeed(const int value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED,value);
                        CBarProgressBar *bar=this.GetProgressBar();
                        if(bar!=NULL)
                           bar.SetMarqueeAnimationSpeed(value);
                       }
   int               MarqueeAnimationSpeed(void)   const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MARQUEE_ANIM_SPEED);  }

//--- (1) Устанавливает, (2) возвращает стиль полосы прогресса
   void              SetStyle(const ENUM_CANV_ELEMENT_PROGRESS_BAR_STYLE style)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE,style);
                        CBarProgressBar *bar=this.GetProgressBar();
                        if(bar!=NULL)
                           bar.SetStyle(style);
                       }
   ENUM_CANV_ELEMENT_PROGRESS_BAR_STYLE Style(void) const
                       { return (ENUM_CANV_ELEMENT_PROGRESS_BAR_STYLE)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STYLE);          }

//--- (1) Устанавливает, (2) возвращает величину приращения значения полосы прогресса для его перерисовки
   void              SetStep(const int value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STEP,value);
                        CBarProgressBar *bar=this.GetProgressBar();
                        if(bar!=NULL)
                           bar.SetStep(value);
                       }
   int               Step(void)                          const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_STEP);    }
   
//--- (1) Устанавливает, (2) возвращает текущее значение полосы прогресса в диапазоне от Min до Max
   void              SetValue(const int value)
                       {
                        this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE,value);
                        CBarProgressBar *bar=this.GetProgressBar();
                        if(bar!=NULL)
                           bar.SetValue(value);
                       }
   int               Value(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_VALUE);   }
   
//--- (1) Устанавливает, (2) возвращает верхнюю границу диапазона, в котором действует ProgressBar
   void              SetMaximum(const int value)               { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM,value);       }
   int               Maximum(void)                       const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MAXIMUM); }
   
//--- (1) Устанавливает, (2) возвращает нижнюю границу диапазона, в котором действует ProgressBar
   void              SetMinimum(const int value)               { this.SetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM,value);       }
   int               Minimum(void)                       const { return (int)this.GetProperty(CANV_ELEMENT_PROP_PROGRESS_BAR_MINIMUM); }
   
//--- Возвращает указатель на объект-полосу прогресса
   CBarProgressBar  *GetProgressBar(void)                { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,0);     }

//--- Поддерживаемые свойства объекта (1) целочисленные, (2) вещественные, (3) строковые
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_INTEGER property) { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_DOUBLE property)  { return true; }
   virtual bool      SupportProperty(ENUM_CANV_ELEMENT_PROP_STRING property)  { return true; }
   
//--- Конструктор
                     CProgressBar(CGCnvElement *main_obj,CGCnvElement *base_obj,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
//--- Таймер
   virtual void      OnTimer(void);
  };
//+------------------------------------------------------------------+

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

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


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

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CProgressBar::CProgressBar(const ENUM_GRAPH_ELEMENT_TYPE type,
                           CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CContainer(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.Initialize();
   this.CreateProgressBar();
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CProgressBar::CProgressBar(CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.Initialize();
   this.CreateProgressBar();
  }
//+------------------------------------------------------------------+

Здесь тип элемента управления жёстко задаётся в строке инициализации.


Метод инициализации свойств элемента:

//+------------------------------------------------------------------+
//| Инициализирует свойства элемента                                 |
//+------------------------------------------------------------------+
void CProgressBar::Initialize(void)
  {
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BACK_COLOR,true);
   this.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true);
   this.SetMarqueeAnimationSpeed(10);
   this.SetMaximum(100);
   this.SetMinimum(0);
   this.SetStep(10);
   this.SetStyle(CANV_ELEMENT_PROGRESS_BAR_STYLE_CONTINUOUS);
   this.SetValue(this.Width()/2);
  }
//+------------------------------------------------------------------+

Рамка объекта устанавливается равной одному пикселю с каждой стороны, тип рамки — простая, устанавливаются цвета объекта и остальные свойства по умолчанию. Длина полосы прогресса устанавливается равной половине ширины объекта.


Метод, создающий объект-полосу прогресса:

//+------------------------------------------------------------------+
//| Создаёт объект-полосу прогресса                                  |
//+------------------------------------------------------------------+
void CProgressBar::CreateProgressBar(void)
  {
//--- Устанавливаем длину полосы прогресса, равной значению Value() объекта
//--- Высота полосы прогресса устанавливается равнойвысоте объекта минус размеры рамки сверху и снизу
   int w=this.Value();
   int h=this.Height()-this.BorderSizeTop()-this.BorderSizeBottom();
//--- Создаём  объект-полосу прогресса
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR,0,0,w,h,clrNONE,255,false,false);
//--- Добавляем созданный объект CProgressBar в список активных элементов коллекции
   if(this.AddObjToListActiveElements())
     {
      //--- Для проверки получим этот элемент из списка, выведем его описание и количество активных элементов в списке
      CProgressBar *progress_bar=this.GetActiveElement(this.IndexActiveElements(this.GetObject()));
      if(progress_bar!=NULL)
         Print(DFUN_ERR_LINE,"CProgressBar: ",progress_bar.TypeElementDescription(),", ListMainActiveElementsTotal=",this.ListMainActiveElementsTotal());
     }
  }
//+------------------------------------------------------------------+

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


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

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

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


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

//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void CProgressBar::OnTimer(void)
  {
   CBarProgressBar *bar=this.GetProgressBar();
   if(bar!=NULL)
      bar.OnTimer();
  }
//+------------------------------------------------------------------+

Здесь: получаем указатель на объект-полосу прогресса и вызываем его обработчик событий таймера. Так как блик должен пробегать именно по полосе прогресса, поэтому в таймере объекта класса CBarProgressBar и будем это реализовывать. И по этой причине здесь и вызывается его таймер, в котором пока написан вывод значения GetTickCount() на график в виде комментария.


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

//+------------------------------------------------------------------+
//| Устанавливает параметры присоединённому объекту                  |
//+------------------------------------------------------------------+
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_SPLIT_CONTAINER)
      obj.SetGroup(this.Group());
//--- В зависимости от типа объекта
   switch(obj.TypeGraphElement())
     {
      //--- Для WinForms-объектов "Контейнер", "Панель", "GroupBox"
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            :
      case GRAPH_ELEMENT_TYPE_WF_PANEL                :
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             :
        obj.SetBorderColor(obj.BackgroundColor(),true);
        break;
      //--- Для WinForms-объектов "Label", "CheckBox", "RadioButton"

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

      //--- Для WinForms-объекта "ToolTip"
      case GRAPH_ELEMENT_TYPE_WF_TOOLTIP              :
        obj.SetBackgroundColor(CLR_DEF_CONTROL_HINT_BACK_COLOR,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_HINT_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_CONTROL_HINT_FORE_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        obj.SetOpacity(0,false);
        obj.SetDisplayed(false);
        obj.Hide();
        break;
      //--- Для WinForms-объекта "BarProgressBar"
      case GRAPH_ELEMENT_TYPE_WF_BAR_PROGRESS_BAR     :
        obj.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BAR_COLOR,true);
        obj.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_NONE);
        break;
      //--- Для WinForms-объекта "ProgressBar"
      case GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR         :
        obj.SetBackgroundColor(CLR_DEF_CONTROL_PROGRESS_BAR_BACK_COLOR,true);
        obj.SetBorderColor(CLR_DEF_CONTROL_PROGRESS_BAR_BORDER_COLOR,true);
        obj.SetForeColor(CLR_DEF_CONTROL_PROGRESS_BAR_FORE_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      default:
        break;
     }
   obj.Crop();
  }
//+------------------------------------------------------------------+

Просто устанавливаем значения по умолчанию, устанавливаемые в конструкторах этих классов.


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

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh пропишем подключение файла элемента управления ProgressBar:

//+------------------------------------------------------------------+
//|                                                        Panel.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "..\Helpers\TabField.mqh"
#include "..\Helpers\ArrowUpButton.mqh"
#include "..\Helpers\ArrowDownButton.mqh"
#include "..\Helpers\ArrowLeftButton.mqh"
#include "..\Helpers\ArrowRightButton.mqh"
#include "..\Helpers\ArrowUpDownBox.mqh"
#include "..\Helpers\ArrowLeftRightBox.mqh"
#include "..\Helpers\HintMoveLeft.mqh"
#include "..\Helpers\HintMoveRight.mqh"
#include "..\Helpers\HintMoveUp.mqh"
#include "..\Helpers\HintMoveDown.mqh"
#include "GroupBox.mqh"
#include "TabControl.mqh"
#include "SplitContainer.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
#include "..\..\WForms\Common Controls\ToolTip.mqh"
#include "..\..\WForms\Common Controls\ProgressBar.mqh"
//+------------------------------------------------------------------+


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

   virtual void      SetPaddingAll(const uint value)
                       {
                        this.SetPaddingLeft(value); this.SetPaddingTop(value); this.SetPaddingRight(value); this.SetPaddingBottom(value);
                       }
//--- Создаёт и присоединяет объект-ToolTip (1) к текущему, (2) к указанному элементу управления
   CToolTip         *SetToolTip(const string tooltip_description,const string tooltip_title,const string tooltip_text,ENUM_CANV_ELEMENT_TOOLTIP_ICON tooltip_ico);
   CToolTip         *SetToolTipTo(CForm *element,const string tooltip_description,const string tooltip_title,const string tooltip_text,ENUM_CANV_ELEMENT_TOOLTIP_ICON tooltip_ico);


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

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


Метод, создающий и присоединяющий объект-ToolTip к текущему элементу:

//+------------------------------------------------------------------+
//| Создаёт и присоединяет объект-ToolTip к текущему элементу        |
//+------------------------------------------------------------------+
CToolTip *CPanel::SetToolTip(const string tooltip_description,const string tooltip_title,const string tooltip_text,ENUM_CANV_ELEMENT_TOOLTIP_ICON tooltip_ico)
  {
//--- Создаём новый объект-всплывающую подсказку
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,0,0,10,10,clrNONE,0,false,false);
//--- Получаем список всех созданных объектов ToolTip
   CArrayObj *list=this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TOOLTIP);
   if(list==NULL || list.Total()==0)
      return NULL;
//--- Получаем последний элемент в списке объектов ToolTip (последний созданный)
   CToolTip *tooltip=list.At(list.Total()-1);
//--- Если объект получен
   if(tooltip!=NULL)
     {
      //--- устанавливаем объекту описание, иконку, заголовок и текст подсказки
      tooltip.SetDescription(tooltip_description);
      tooltip.SetIcon(tooltip_ico);
      tooltip.SetTitle(tooltip_title);
      tooltip.SetTooltipText(tooltip_text);
     }
//--- Возвращаем объект ToolTip
   return tooltip;
  }
//+------------------------------------------------------------------+

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


Метод, создающий и присоединяющий объект-ToolTip к указанному элементу:

//+------------------------------------------------------------------+
//| Создаёт и присоединяет объект-ToolTip к указанному элементу      |
//+------------------------------------------------------------------+
CToolTip *CPanel::SetToolTipTo(CForm *element,const string tooltip_description,const string tooltip_title,const string tooltip_text,ENUM_CANV_ELEMENT_TOOLTIP_ICON tooltip_ico)
  {
   CToolTip *tooltip=this.SetToolTip(tooltip_description,tooltip_title,tooltip_text,tooltip_ico);
   if(tooltip==NULL)
      return NULL;
   if(element.AddTooltip(tooltip))
      return tooltip;
   return NULL;
  }
//+------------------------------------------------------------------+

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


Идентичные доработки по присоединению объектов-всплывающих подсказок и созданию элементов управления ProgressBar сделаны во всех файлах классов-контейнеров:

TabControl.mqh, TabField.mqh, SplitContainer.mqh, SplitContainerPanel.mqh и GroupBox.mqh. Здесь эти изменения рассматривать не будем.


Доработаем класс-коллекцию графических элементов в файле \MQL5\Include\DoEasy\Collections\GraphElementsCollection.mqh.

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

В приватной секции класса-коллекции объявим список объектов для обработки:

   SDataPivotPoint   m_data_pivot_point[];      // Массив-структуры данных опорной точки
   CArrayObj         m_list_charts_control;     // Список объектов управления чартами
   CListObj          m_list_all_canv_elm_obj;   // Список всех графических элементов на канвасе
   CListObj          m_list_elm_to_process;     // Список графических элементов на канвасе для обработки
   CListObj          m_list_all_graph_obj;      // Список всех графических объектов
   CArrayObj         m_list_deleted_obj;        // Список удалённых графических объектов
   CMouseState       m_mouse;                   // Объект класса "Состояния мышки"


Там же объявим методы для обработки текущего и прошлого объектов под курсором, и методы для работы со списком объектов для обработки:

//--- Сбрасывает всем формам флаги взаимодействия кроме указанной
   void              ResetAllInteractionExeptOne(CGCnvElement *form);
//--- Постобработка бывшей активной формы под курсором
   void              FormPostProcessing(CForm *form,const int id, const long &lparam, const double &dparam, const string &sparam);
//--- Обработка (1) текущего, (2) прошлого элемента ToolTip
   void              TooltipCurrProcessing(CForm *tooltip,CForm *base);
   void              TooltipPrevProcessing(CForm *tooltip);
//--- Добавляет элемент (1) в список-коллекцию, (2) в список для обработки
   bool              AddCanvElmToCollection(CGCnvElement *element);
   bool              AddCanvElmToProcessList(CForm *element);
//--- Получает элемент по имени из списка для обработки
   CForm            *GetCanvElementFromProcessList(CForm *element);
//--- Возвращает индекс элемента по имени в списке для обработки
   int               CanvElementIndexInProcessList(CForm *element) const;
//--- Добавляет элемент в список-коллекцию либо возвращает существующий
   ENUM_ADD_OBJ_RET_CODE AddOrGetCanvElmToCollection(CGCnvElement *element,int &id);
//--- Возвращает индекс графического элемента в списке-коллекции
   int               GetIndexGraphElement(const long chart_id,const string name);


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

public:
//--- Возвращает себя
   CGraphElementsCollection *GetObject(void)                                                             { return &this;                        }
//--- Возвращает полный список-коллекцию стандартных графических объектов "как есть"
   CArrayObj        *GetListGraphObj(void)                                                               { return &this.m_list_all_graph_obj;   }
//--- Возвращает (1) полный список-коллекцию графических элеменов на канвасе "как есть", (2) список элементов для обработки
   CArrayObj        *GetListCanvElm(void)                                                                { return &this.m_list_all_canv_elm_obj;}
   CArrayObj        *GetListCanvElmToProcess(void)                                                       { return &this.m_list_elm_to_process;  }
//--- Возвращает список графических элементов по выбранному (1) целочисленному, (2) вещественному и (3) строковому свойству, удовлетворяющему сравниваемому критерию
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_INTEGER property,long value,ENUM_COMPARER_TYPE mode=EQUAL)             { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);           }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_DOUBLE property,double value,ENUM_COMPARER_TYPE mode=EQUAL)            { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);           }
   CArrayObj        *GetList(ENUM_CANV_ELEMENT_PROP_STRING property,string value,ENUM_COMPARER_TYPE mode=EQUAL)            { return CSelect::ByGraphCanvElementProperty(this.GetListCanvElm(),property,value,mode);           }

и объявим обработчик события таймера коллекции:

//--- (1) Обработчики событий, (2) таймер, (3) деинициализация
   void              OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam);
   void              OnTimer();
   void              OnDeinit(void);


В конструкторе класса очистим список и выставим ему флаг сортированного списка:

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CGraphElementsCollection::CGraphElementsCollection()
  {
   this.m_type=COLLECTION_GRAPH_OBJ_ID;
   this.m_name_prefix=this.m_name_program+"_";
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_MOVE,true);
   ::ChartSetInteger(::ChartID(),CHART_EVENT_MOUSE_WHEEL,true);
   this.m_list_all_graph_obj.Type(COLLECTION_GRAPH_OBJ_ID);
   this.m_list_all_graph_obj.Sort(SORT_BY_CANV_ELEMENT_ID);
   this.m_list_all_graph_obj.Clear();
   this.m_list_charts_control.Sort();
   this.m_list_charts_control.Clear();
   this.m_total_objects=0;
   this.m_is_graph_obj_event=false;
   this.m_list_deleted_obj.Clear();
   this.m_list_deleted_obj.Sort();
   this.m_list_all_canv_elm_obj.Clear();
   this.m_list_all_canv_elm_obj.Sort();
   this.m_list_elm_to_process.Clear();
   this.m_list_elm_to_process.Sort();
  }
//+------------------------------------------------------------------+


Метод, добавляющий графический элемент на канвасе в спиcок для обработки:

//+------------------------------------------------------------------+
//| Добавляет графический элемент на канвасе в спиcок для обработки  |
//+------------------------------------------------------------------+
bool CGraphElementsCollection::AddCanvElmToProcessList(CForm *element)
  {
   if(this.GetCanvElementFromProcessList(element)!=NULL)
      return false;
   if(!this.m_list_elm_to_process.Add(element))
     {
      CMessage::ToLog(DFUN+element.TypeElementDescription()+element.Name()+": ",MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      return false;
     }
   return true;
  }
//+------------------------------------------------------------------+

Получаем указатель на список объектов для обработки. Если не удалось добавить в него переданный в метод объект — сообщаем об этом в журнал и возвращаем false. При успешном добавлении возвращаем true.


Метод, получающий элемент по имени из списка для обработки:

//+------------------------------------------------------------------+
//| Получает элемент по имени из списка для обработки                |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::GetCanvElementFromProcessList(CForm *element)
  {
   for(int i=this.m_list_elm_to_process.Total()-1;i>WRONG_VALUE;i--)
     {
      CForm *obj=this.m_list_elm_to_process.At(i);
      if(obj==NULL)
         continue;
      if(obj.Name()==element.Name())
         return obj;
     }
   return NULL;
  }
//+------------------------------------------------------------------+

В цикле по списку объектов для обработки получаем очередной объект и, если его имя совпадает с именем переданного в метод объекта — возвращаем указатель на найденный объект. По окончании цикла возвращаем NULL — объект не найден.


Метод, возвращающий индекс элемента по имени в списке для обработки:

//+------------------------------------------------------------------+
//| Возвращает индекс элемента по имени в списке для обработки       |
//+------------------------------------------------------------------+
int CGraphElementsCollection::CanvElementIndexInProcessList(CForm *element) const
  {
   for(int i=this.m_list_elm_to_process.Total()-1;i>WRONG_VALUE;i--)
     {
      CForm *obj=this.m_list_elm_to_process.At(i);
      if(obj==NULL)
         continue;
      if(obj.Name()==element.Name())
         return i;
     }
   return WRONG_VALUE;
  }
//+------------------------------------------------------------------+
В цикле по списку объектов для обработки получаем очередной объект и, если его имя совпадает с именем переданного в метод объекта — возвращаем индекс цикла. По окончании цикла возвращаем -1, что означает что объект не найден.


Метод обработки текущего элемента ToolTip:

//+------------------------------------------------------------------+
//| Обработка текущего элемента ToolTip                              |
//+------------------------------------------------------------------+
void CGraphElementsCollection::TooltipCurrProcessing(CForm *tooltip,CForm *base)
  {
//--- Если передан хотя бы один пустой объект - уходим
   if(tooltip==NULL || base==NULL)
      return;
//--- Получаем ширину и высоту графика
   int w=(int)::ChartGetInteger(tooltip.ChartID(),CHART_WIDTH_IN_PIXELS,tooltip.SubWindow());
   int h=(int)::ChartGetInteger(tooltip.ChartID(),CHART_HEIGHT_IN_PIXELS,tooltip.SubWindow());
//--- Получаем координаты курсора
   int x=this.m_mouse.CoordX();
   int y=this.m_mouse.CoordY();
//--- Если при текущей координате X (курсор) подсказка будет выходить за правый край графика - корректируем координату X
   if(x+tooltip.Width()>w)
      x=w-tooltip.Width();
//--- Если при текущей координате Y (курсор) подсказка будет выходить за нижний край графика - корректируем координату Y
   if(y+tooltip.Height()>h)
      y=h-tooltip.Height();
//--- Если объект-подсказка смещён на полученные координаты курсора
   if(tooltip.Move(x,y))
     {
      //--- Устанавливаем новые относительные координаты подсказки
      tooltip.SetCoordXRelative(tooltip.CoordX()-base.CoordX());
      tooltip.SetCoordYRelative(tooltip.CoordY()-base.CoordY());
     }
  }
//+------------------------------------------------------------------+

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


Метод обработки прошлого элемента ToolTip:

//+------------------------------------------------------------------+
//| Обработка прошлого элемента ToolTip                              |
//+------------------------------------------------------------------+
void CGraphElementsCollection::TooltipPrevProcessing(CForm *tooltip)
  {
//--- Если передан пустой объект - уходим
   if(tooltip==NULL)
      return;
//--- Устанавливаем флаг неотображения объекта, делаем его полностью прозрачным и скрываем
   tooltip.SetDisplayed(false);
   tooltip.SetOpacity(0,false);
   tooltip.SetDisplayState(CANV_ELEMENT_DISPLAY_STATE_NORMAL);
   tooltip.Hide();
//--- Получаем индекс объекта в списке
   int index=this.CanvElementIndexInProcessList(tooltip);
//--- Если индекс получен - изымаем объект из списка
   if(index>WRONG_VALUE)
      this.m_list_elm_to_process.DetachElement(index);
  }
//+------------------------------------------------------------------+

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

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

      //--- Объявим статические переменные для активной формы и флагов состояний
      static CForm *form=NULL;

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

      //--- Если событие перемещения мышки
      if(id==CHARTEVENT_MOUSE_MOVE)
        {
         //--- Если курсор над формой
         if(form!=NULL)
           {
            //--- Если стоит флаг перемещения

Только данные, записанные в указателе, будут ссылаться уже совсем не на область памяти этого объекта. Отсюда и завершение программы при обращении в неправильное место в памяти. Исправить ошибку легко: проверять будем валидность указателя, а не его значение:

      //--- Если событие перемещения мышки
      if(id==CHARTEVENT_MOUSE_MOVE)
        {
         //--- Если курсор над формой
         if(::CheckPointer(form)!=POINTER_INVALID)
           {
            //--- Если стоит флаг перемещения
            if(move)
              {


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

            //--- Если флаг перемещения снят
            else
              {
               //--- Получаем объект ToolTip, назначенный форме, и объявляем предыдущий ToolTip
               CForm *tooltip=form.GetTooltip();
               static CForm *tooltip_prev=NULL;
               //--- Если объект ToolTip получен
               if(tooltip!=NULL && tooltip.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
                 {
                  //--- Если прошлый объект-ToolTip существует
                  if(::CheckPointer(tooltip_prev)!=POINTER_INVALID && tooltip_prev.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
                    {
                     //--- Если имя прошлого ToolTip не равно имени текущего - скрываем прошлый объект-ToolTip
                     if(tooltip_prev.Name()!=tooltip.Name())
                        this.TooltipPrevProcessing(tooltip_prev);
                    }
                    
                  //--- Получаем базовый объект, записанный в объекте ToolTip
                  CForm *base=tooltip.GetBase();
                  //--- Если базовый объект получен
                  //--- и если имя базового объекта такое же, как и имя текущей формы (этот ToolTip привязан к этой форме)
                  if(base!=NULL && base.Name()==form.Name())
                    {
                     //--- Добавляем ToolTip в список обработки, смещаем его в координаты курсора
                     //--- и записываем в переменную текущий ToolTip как прошлый
                     if(this.AddCanvElmToProcessList(tooltip))
                       {
                        this.TooltipCurrProcessing(tooltip,base);
                        tooltip_prev=tooltip;
                       }
                    }
                 }
               //--- Если нет текущего объекта-ToolTip, но прошлый ToolTip существует - скрываем прошлый объект-ToolTip
               else if(::CheckPointer(tooltip_prev)!=POINTER_INVALID && tooltip_prev.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
                  this.TooltipPrevProcessing(tooltip_prev);
               
               //--- Если в 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);
              }

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


Обработчик событий таймера коллекции:

//+------------------------------------------------------------------+
//| Таймер                                                           |
//+------------------------------------------------------------------+
void CGraphElementsCollection::OnTimer(void)
  {
//--- Работаем с элементами под курсором
//--- В цикле по списку объектов для обработки
   for(int i=this.m_list_elm_to_process.Total()-1;i>WRONG_VALUE;i--)
     {
      //--- получаем очередной объект из списка
      CForm *obj=this.m_list_elm_to_process.At(i);
      if(obj==NULL)
         continue;
      //--- Если состояние объекта - завершён полный цикл
      if(obj.DisplayState()==CANV_ELEMENT_DISPLAY_STATE_COMPLETED)
        {
         //--- изымаем указатель на объект из списка и идём на следующую итерацию
         this.m_list_elm_to_process.DetachElement(i);
         continue;
        }
      //--- Вызываем обработчик событий таймера объекта
      obj.OnTimer();
     }

//--- Работаем с активными элементами коллекции
//--- В цикле по списку-коллекции графических элементов
   for(int i=0;i<this.m_list_all_canv_elm_obj.Total();i++)
     {
      //--- Получаем очередной объект из списка
      CWinFormBase *obj=this.m_list_all_canv_elm_obj.At(i);
      if(obj==NULL)
         continue;
      //--- Получаем из объекта список активных элементов
      CArrayObj *list=obj.GetListMainActiveElements();
      if(list==NULL || list.Total()==0)
         continue;
      //--- В цикле по списку активных элементов
      for(int j=0;j<list.Total();j++)
        {
         //--- получаем очередной объект и
         CWinFormBase *elm=list.At(j);
         if(elm==NULL)
            continue;
         //--- вызываем обработчик событий таймера объекта
         elm.OnTimer();
        }
     }
  }
//+------------------------------------------------------------------+

Логика обработчика полностью расписана в комментариях к коду. Работаем в двух циклах — по списку объектов для обработки (ToolTip) и по списку активных элементов (объектов с визуальными анимационными эффектами)


В главном объекте библиотеки CEngine в файле \MQL5\Include\DoEasy\Engine.mqh нужно создать таймер графических элементов библиотеки и обработчик событий таймера.

В приватной секции объявим обработчик:

//--- Работа с событиями (1) ордеров, сделок и позиций, (2) аккаунтов, (3) графических объектов, (4) элементов на канвасе
   void                 TradeEventsControl(void);
   void                 AccountEventsControl(void);
   void                 GraphObjEventsControl(void);
   void                 GraphElmEventsControl(void);
//--- (1) Работа с коллекцией символов и (2) событиями списка символов в окне обзора рынка


В конструкторе класса создадим ещё один таймер — таймер коллекции графических элементов:

//+------------------------------------------------------------------+
//| CEngine конструктор                                              |
//+------------------------------------------------------------------+
CEngine::CEngine() : m_first_start(true),
                     m_last_trade_event(TRADE_EVENT_NO_EVENT),
                     m_last_account_event(WRONG_VALUE),
                     m_last_symbol_event(WRONG_VALUE),
                     m_global_error(ERR_SUCCESS)
  {
   this.m_is_hedge=#ifdef __MQL4__ true #else bool(::AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) #endif;
   this.m_is_tester=::MQLInfoInteger(MQL_TESTER);
   this.m_program_type=(ENUM_PROGRAM_TYPE)::MQLInfoInteger(MQL_PROGRAM_TYPE);
   this.m_name_program=::MQLInfoString(MQL_PROGRAM_NAME);
   this.m_name_prefix=this.m_name_program+"_";
   
   this.m_list_counters.Sort();
   this.m_list_counters.Clear();
   this.CreateCounter(COLLECTION_ORD_COUNTER_ID,COLLECTION_ORD_COUNTER_STEP,COLLECTION_ORD_PAUSE);
   this.CreateCounter(COLLECTION_ACC_COUNTER_ID,COLLECTION_ACC_COUNTER_STEP,COLLECTION_ACC_PAUSE);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID1,COLLECTION_SYM_COUNTER_STEP1,COLLECTION_SYM_PAUSE1);
   this.CreateCounter(COLLECTION_SYM_COUNTER_ID2,COLLECTION_SYM_COUNTER_STEP2,COLLECTION_SYM_PAUSE2);
   this.CreateCounter(COLLECTION_REQ_COUNTER_ID,COLLECTION_REQ_COUNTER_STEP,COLLECTION_REQ_PAUSE);
   this.CreateCounter(COLLECTION_TS_COUNTER_ID,COLLECTION_TS_COUNTER_STEP,COLLECTION_TS_PAUSE);
   this.CreateCounter(COLLECTION_IND_TS_COUNTER_ID,COLLECTION_IND_TS_COUNTER_STEP,COLLECTION_IND_TS_PAUSE);
   this.CreateCounter(COLLECTION_TICKS_COUNTER_ID,COLLECTION_TICKS_COUNTER_STEP,COLLECTION_TICKS_PAUSE);
   this.CreateCounter(COLLECTION_CHARTS_COUNTER_ID,COLLECTION_CHARTS_COUNTER_STEP,COLLECTION_CHARTS_PAUSE);
   this.CreateCounter(COLLECTION_GRAPH_OBJ_COUNTER_ID,COLLECTION_GRAPH_OBJ_COUNTER_STEP,COLLECTION_GRAPH_OBJ_PAUSE);
   this.CreateCounter(COLLECTION_GRAPH_ELM_COUNTER_ID,COLLECTION_GRAPH_ELM_COUNTER_STEP,COLLECTION_GRAPH_ELM_PAUSE);
   
   ::ResetLastError();
   #ifdef __MQL5__
      if(!::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   //---__MQL4__
   #else 
      if(!this.IsTester() && !::EventSetMillisecondTimer(TIMER_FREQUENCY))
        {
         ::Print(DFUN_ERR_LINE,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_TIMER),(string)::GetLastError());
         this.m_global_error=::GetLastError();
        }
   #endif 
   //---
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| CEngine таймер                                                   |
//+------------------------------------------------------------------+
void CEngine::OnTimer(SDataCalculate &data_calculate)
  {
//--- Если это не тестер - работаем с событиями коллекций по таймеру
   if(!this.IsTester())
     {
   //--- Таймер коллекций исторических ордеров и сделок и рыночных ордеров и позиций
      int index=this.CounterIndex(COLLECTION_ORD_COUNTER_ID);
      CTimerCounter* cnt1=this.m_list_counters.At(index);
      if(cnt1!=NULL)
        {
         //--- Если пауза завершилась - работаем с событиями коллекций ордеров, сделок и позиций
         if(cnt1.IsTimeDone())
            this.TradeEventsControl();
        }
   //--- Таймер коллекции аккаунтов
      index=this.CounterIndex(COLLECTION_ACC_COUNTER_ID);
      CTimerCounter* cnt2=this.m_list_counters.At(index);
      if(cnt2!=NULL)
        {
         //--- Если пауза завершилась - работаем с событиями коллекции аккаунтов
         if(cnt2.IsTimeDone())
            this.AccountEventsControl();
        }
   //--- Таймер1 коллекции символов (обновление котировочных данных символов в коллекции)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID1);
      CTimerCounter* cnt3=this.m_list_counters.At(index);
      if(cnt3!=NULL)
        {
         //--- Если пауза завершилась - обновляем котировочные данные всех символов в коллекции
         if(cnt3.IsTimeDone())
            this.m_symbols.RefreshRates();
        }
   //--- Таймер2 коллекции символов (обновление всех данных всех символов в коллекции и отслеживание событий символов и списка символов в окне обзора рынка)
      index=this.CounterIndex(COLLECTION_SYM_COUNTER_ID2);
      CTimerCounter* cnt4=this.m_list_counters.At(index);
      if(cnt4!=NULL)
        {
         //--- Если пауза завершилась
         if(cnt4.IsTimeDone())
           {
            //--- обновляем данные и работаем с событиями всех символов в коллекции
            this.SymbolEventsControl();
            //--- Если работаем со списком из обзора рынка - проверяем события окна обзора рынка
            if(this.m_symbols.ModeSymbolsList()==SYMBOLS_MODE_MARKET_WATCH)
               this.MarketWatchEventsControl();
           }
        }
   //--- Таймер торгового класса
      index=this.CounterIndex(COLLECTION_REQ_COUNTER_ID);
      CTimerCounter* cnt5=this.m_list_counters.At(index);
      if(cnt5!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком отложенных запросов
         if(cnt5.IsTimeDone())
            this.m_trading.OnTimer();
        }
   //--- Таймер коллекции таймсерий
      index=this.CounterIndex(COLLECTION_TS_COUNTER_ID);
      CTimerCounter* cnt6=this.m_list_counters.At(index);
      if(cnt6!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком таймсерий (обновляем все кроме текущей)
         if(cnt6.IsTimeDone())
            this.SeriesRefreshAllExceptCurrent(data_calculate);
        }
        
   //--- Таймер коллекции таймсерий данных индикаторных буферов
      index=this.CounterIndex(COLLECTION_IND_TS_COUNTER_ID);
      CTimerCounter* cnt7=this.m_list_counters.At(index);
      if(cnt7!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком таймсерий индикаторных данных (обновляем все кроме текущей)
         if(cnt7.IsTimeDone()) 
            this.IndicatorSeriesRefreshAll();
        }
   //--- Таймер коллекции тиковых серий
      index=this.CounterIndex(COLLECTION_TICKS_COUNTER_ID);
      CTimerCounter* cnt8=this.m_list_counters.At(index);
      if(cnt8!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком тиковых серий (обновляем все кроме текущей)
         if(cnt8.IsTimeDone())
            this.TickSeriesRefreshAllExceptCurrent();
        }
   //--- Таймер коллекции чартов
      index=this.CounterIndex(COLLECTION_CHARTS_COUNTER_ID);
      CTimerCounter* cnt9=this.m_list_counters.At(index);
      if(cnt9!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком чартов
         if(cnt9.IsTimeDone())
            this.ChartsRefreshAll();
        }
        
   //--- Таймер коллекции графических объектов
      index=this.CounterIndex(COLLECTION_GRAPH_OBJ_COUNTER_ID);
      CTimerCounter* cnt10=this.m_list_counters.At(index);
      if(cnt10!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком графических объектов
         if(cnt10.IsTimeDone())
            this.GraphObjEventsControl();
        }
        
   //--- Таймер коллекции графических элементов на канвасе
      index=this.CounterIndex(COLLECTION_GRAPH_ELM_COUNTER_ID);
      CTimerCounter* cnt11=this.m_list_counters.At(index);
      if(cnt11!=NULL)
        {
         //--- Если пауза завершилась - работаем со списком графических элементов на канвасе
         if(cnt11.IsTimeDone())
            this.GraphElmEventsControl();
        }
        
     }
//--- Если тестер - работаем с событиями коллекций по тику
   else
     {
      //--- работаем с событиями коллекций ордеров, сделок и позиций по тику
      this.TradeEventsControl();
      //--- работаем с событиями коллекции аккаунтов по тику
      this.AccountEventsControl();
      //--- обновляем котировочные данные всех символов в коллекции по тику
      this.m_symbols.RefreshRates();
      //--- работаем с событиями всех символов в коллекции по тику
      this.SymbolEventsControl();
      //--- работаем со списком отложенных запросов по тику
      this.m_trading.OnTimer();
      //--- работаем со списком таймсерий по тику
      this.SeriesRefresh(data_calculate);
      //--- работаем со списком таймсерий индикаторных буферов по тику
      this.IndicatorSeriesRefreshAll();
      //--- работаем со списком тиковых серий по тику
      this.TickSeriesRefreshAll();
      //--- работаем со списком графических объектов по тику
      this.GraphObjEventsControl();
      //--- работаем со списком графических элементов на канвасе по тику
      this.GraphElmEventsControl();
     }
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Обработка событий графических элементов на канвасе               |
//+------------------------------------------------------------------+
void CEngine::GraphElmEventsControl(void)
  {
   this.m_graph_objects.OnTimer();
  }
//+------------------------------------------------------------------+

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

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


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

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

В обработчике OnInit() советника на первой вкладке элемента управления TabControl в элементе правления SplitContainer, на его второй панели создадим элемент управления ProgressBar:

                  //--- На каждой из панелей элемента ...
                  for(int j=0;j<2;j++)
                    {
                     //--- Получаем панель по индексу цикла
                     CSplitContainerPanel *panel=split_container.GetPanel(j);
                     if(panel==NULL)
                        continue;
                     //--- устанавливаем для панели её описание
                     panel.SetDescription(TextByLanguage("Панель","Panel")+string(j+1));
                     
                     //--- Если это первая вкладка и вторая панель
                     if(n==0 && j==1)
                       {
                        //--- Создадим на ней элемент управления ProgressBar
                        if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR,4,4,100,12,clrNONE,255,false,false))
                          {
                           CProgressBar *progress_bar=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_PROGRESS_BAR,0);
                           if(progress_bar!=NULL)
                             {
                              Print(DFUN,progress_bar.TypeElementDescription()," ",progress_bar.Name());
                             }
                          }
                       }
                     
                     //--- ... создадим текстовую метку с названием панели
                     if(split_container.CreateNewElement(j,GRAPH_ELEMENT_TYPE_WF_LABEL,4,4,panel.Width()-8,panel.Height()-8,clrDodgerBlue,255,true,false))
                       {
                        CLabel *label=split_container.GetPanelElementByType(j,GRAPH_ELEMENT_TYPE_WF_LABEL,0);
                        if(label==NULL)
                           continue;
                        label.SetTextAlign(ANCHOR_CENTER);
                        label.SetText(panel.Description());
                       }
                    }

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

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


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


Что дальше

В следующей статье продолжим работу над объектом ProgressBar.

Ниже прикреплены все файлы текущей версии библиотеки, файлы тестового советника и индикатора контроля событий графиков для MQL5.

К содержанию

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

 
DoEasy. Элементы управления (Часть 20): WinForms-объект SplitContainer
DoEasy. Элементы управления (Часть 21): Элемент управления SplitContainer. Разделитель панелей
DoEasy. Элементы управления (Часть 22): SplitContainer. Изменение свойств созданного объекта
DoEasy. Элементы управления (Часть 23): дорабатываем WinForms-объекты TabControl и SplitContainer
DoEasy. Элементы управления (Часть 24): Вспомогательный WinForms-объект "Подсказка"
DoEasy. Элементы управления (Часть 25): WinForms-объект "Tooltip"