English 中文 Español Deutsch 日本語 Português
preview
DoEasy. Элементы управления (Часть 25): WinForms-объект "Tooltip"

DoEasy. Элементы управления (Часть 25): WinForms-объект "Tooltip"

MetaTrader 5Примеры | 4 ноября 2022, 15:28
834 0
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

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

Кроме того, если обратиться за примером к MS Visual Studio, то создаваемый объект ToolTip виден в объекте-форме и его можно назначить для объектов, привязанных к этой форме. Мы сделаем таким образом: те объекты, которые будут видеть класс объекта-всплывающей подсказки, будут иметь возможность её создавать, а все остальные объекты будут иметь возможность присоединить к себе ранее созданный такой объект. Для этого им не обязательно его "знать", так как в список ArrayObj можно добавлять любой объект, производный от базового класса Стандартной Библиотеки.

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

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

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


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

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

//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Стандартный графический объект
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Расширенный стандартный графический объект
   GRAPH_ELEMENT_TYPE_SHADOW_OBJ,                     // Объект тени
   GRAPH_ELEMENT_TYPE_ELEMENT,                        // Элемент
   GRAPH_ELEMENT_TYPE_FORM,                           // Форма
   GRAPH_ELEMENT_TYPE_WINDOW,                         // Окно
   //--- WinForms
   GRAPH_ELEMENT_TYPE_WF_UNDERLAY,                    // Подложка объекта-панели
   GRAPH_ELEMENT_TYPE_WF_BASE,                        // Windows Forms Base
   //--- Ниже нужно вписывать типы объектов "контейнер"
   GRAPH_ELEMENT_TYPE_WF_CONTAINER,                   // Windows Forms базовый объект-контейнер
   GRAPH_ELEMENT_TYPE_WF_PANEL,                       // Windows Forms Panel
   GRAPH_ELEMENT_TYPE_WF_GROUPBOX,                    // Windows Forms GroupBox
   GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,                 // Windows Forms TabControl
   GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER,             // Windows Forms SplitContainer
   //--- Ниже нужно вписывать типы объектов "стандартный элемент управления"
   GRAPH_ELEMENT_TYPE_WF_COMMON_BASE,                 // Windows Forms базовый стандартный элемент управления
   GRAPH_ELEMENT_TYPE_WF_LABEL,                       // Windows Forms Label
   GRAPH_ELEMENT_TYPE_WF_BUTTON,                      // Windows Forms Button
   GRAPH_ELEMENT_TYPE_WF_CHECKBOX,                    // Windows Forms CheckBox
   GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON,                 // Windows Forms RadioButton
   GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX,           // Базовый объект-список Windows Forms элементов
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX,                    // Windows Forms ListBox
   GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,            // Windows Forms CheckedListBox
   GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,             // Windows Forms ButtonListBox
   GRAPH_ELEMENT_TYPE_WF_TOOLTIP,                     // Windows Forms ToolTip
   //--- Вспомогательные элементы 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
  };
//+------------------------------------------------------------------+


Чтобы была возможность рисования некоторых стандартных изображений, таких как значки "Info", "Warning", "Error" и т.п. создадим перечисление с типами таких значков-иконок:

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

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


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

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

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

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

   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,// Флаг свёрнутости панели 1
   CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE, // Минимальный размер панели 2
   CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY,           // Задержка отображения подсказки
   CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY,          // Длительность отображения подсказки
   CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,            // Задержка отображения новой подсказки одного элемента
   CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS,             // Отображать подсказку в неактивном окне
   CANV_ELEMENT_PROP_TOOLTIP_ICON,                    // Значок, отображаемый в подсказке
   CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,              // Подсказка в форме "облачка"
   CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,              // Угасание при отображении и скрытии подсказки
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (129)         // Общее количество целочисленных свойств
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Количество неиспользуемых в сортировке целочисленных свойств
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Строковые свойства графического элемента на канвасе              |
//+------------------------------------------------------------------+
enum ENUM_CANV_ELEMENT_PROP_STRING
  {
   CANV_ELEMENT_PROP_NAME_OBJ = (CANV_ELEMENT_PROP_INTEGER_TOTAL+CANV_ELEMENT_PROP_DOUBLE_TOTAL), // Имя объекта-графического элемента
   CANV_ELEMENT_PROP_NAME_RES,                        // Имя графического ресурса
   CANV_ELEMENT_PROP_TEXT,                            // Текст графического элемента
   CANV_ELEMENT_PROP_DESCRIPTION,                     // Описание графического элемента
   CANV_ELEMENT_PROP_TOOLTIP_TITLE,                   // Заголовок подсказки элемента
   CANV_ELEMENT_PROP_TOOLTIP_TEXT,                    // Текст подсказки элемента
  };
#define CANV_ELEMENT_PROP_STRING_TOTAL  (6)           // Общее количество строковых свойств
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Возможные критерии сортировки графических элементов на канвасе   |
//+------------------------------------------------------------------+
#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_SPLIT_CONTAINER_PANEL2_COLLAPSED,// Сортировать по флагу свёрнутости панели 1
   SORT_BY_CANV_ELEMENT_SPLIT_CONTAINER_PANEL2_MIN_SIZE, // Сортировать по минимальному размеру панели 2
   SORT_BY_CANV_ELEMENT_TOOLTIP_INITIAL_DELAY,        // Сортировать по задержке отображения подсказки
   SORT_BY_CANV_ELEMENT_TOOLTIP_AUTO_POP_DELAY,       // Сортировать по длительности отображения подсказки
   SORT_BY_CANV_ELEMENT_TOOLTIP_RESHOW_DELAY,         // Сортировать по задержке отображения новой подсказки одного элемента
   SORT_BY_CANV_ELEMENT_TOOLTIP_SHOW_ALWAYS,          // Сортировать по отображению подсказки в неактивном окне
   SORT_BY_CANV_ELEMENT_TOOLTIP_ICON,                 // Сортировать по значку, отображаемому в подсказке
   SORT_BY_CANV_ELEMENT_TOOLTIP_IS_BALLOON,           // Сортировать по флагу подсказки в форме "облачка"
   SORT_BY_CANV_ELEMENT_TOOLTIP_USE_FADING,           // Сортировать по флагу угасания при отображении и скрытии подсказки
//--- Сортировка по вещественным свойствам

//--- Сортировка по строковым свойствам
   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 добавим индексы новых сообщений:

//--- CForm
   MSG_FORM_OBJECT_TEXT_NO_SHADOW_OBJ_FIRST_CREATE_IT,// Отсутствует объект тени. Необходимо сначала его создать при помощи метода CreateShadowObj()
   MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ,      // Не удалось создать новый объект для тени
   MSG_FORM_OBJECT_ERR_FAILED_CREATE_PC_OBJ,          // Не удалось создать новый объект-копировщик пикселей
   MSG_FORM_OBJECT_PC_OBJ_ALREADY_IN_LIST,            // В списке уже есть объект-копировщик пикселей с идентификатором 
   MSG_FORM_OBJECT_PC_OBJ_NOT_EXIST_LIST,             // В списке нет объекта-копировщика пикселей с идентификатором 
   MSG_FORM_OBJECT_ERR_NOT_INTENDED,                  // Метод не предназначен для создания такого объекта: 
   MSG_FORM_TOOLTIP_OBJ_ALREADY_EXISTS,               // Объект ToolTip уже существует

//--- CFrame

...

   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,            // Элемент управления HintMoveLeft
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,          // Элемент управления HintMoveLeft
   MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP,                 // Элемент управления ToolTip
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Графический объект принадлежит программе
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Графический объект не принадлежит программе

...

   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,  // Флаг свёрнутости панели 1
   MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,   // Минимальный размер панели 2
   MSG_CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY,       // Задержка отображения подсказки
   MSG_CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY,      // Длительность отображения подсказки
   MSG_CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,        // Задержка отображения новой подсказки одного элемента
   MSG_CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS,         // Отображать подсказку в неактивном окне
   MSG_CANV_ELEMENT_PROP_TOOLTIP_ICON,                // Значок, отображаемый в подсказке
   MSG_CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,          // Подсказка в форме "облачка"
   MSG_CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,          // Угасание при отображении и скрытии подсказки
   
//--- Вещественные свойства графических элементов

//--- Строковые свойства графических элементов
   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,                // Текст подсказки элемента
  };
//+------------------------------------------------------------------+

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

//--- CForm
   {"Отсутствует объект тени. Необходимо сначала его создать при помощи метода CreateShadowObj()","There is no shadow object. You must first create it using the CreateShadowObj () method"},
   {"Не удалось создать новый объект для тени","Failed to create new object for shadow"},
   {"Не удалось создать новый объект-копировщик пикселей","Failed to create new pixel copier object"},
   {"В списке уже есть объект-копировщик пикселей с идентификатором ","There is already a pixel copier object in the list with ID "},
   {"В списке нет объекта-копировщика пикселей с идентификатором ","No pixel copier object with ID "},
   {"Метод не предназначен для создания такого объекта: ","The method is not intended to create such an object: "},
   {"Объект ToolTip уже существует","ToolTip object already exists"},
   
//--- CFrame

...

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

...

   {"Флаг свёрнутости панели 2","Flag to indicate that panel 2 is collapsed"},
   {"Минимальный размер панели 2","Min size of Panel 2"},
   {"Задержка отображения подсказки","Tooltip initial delay"},
   {"Длительность отображения подсказки","Tooltip autopoop delay"},
   {"Задержка отображения новой подсказки одного элемента","Tooltip reshow delay"},
   {"Отображать подсказку в неактивном окне","Tooltip show always"},
   {"Значок, отображаемый в подсказке","Tooltip icon"},
   {"Подсказка в форме \"облачка\"","Tooltip as \"Balloon\""},
   {"Угасание при отображении и скрытии подсказки","Tooltip uses fading"},

//--- Строковые свойства графических элементов
   {"Имя объекта-графического элемента","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"},
  };
//+---------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Возвращает описание типа графического элемента                   |
//+------------------------------------------------------------------+
string CGBaseObj::TypeElementDescription(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return
     (
      type==GRAPH_ELEMENT_TYPE_STANDARD                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD)                 :
      type==GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED         ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED)        :
      type==GRAPH_ELEMENT_TYPE_ELEMENT                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_ELEMENT)                  :
      type==GRAPH_ELEMENT_TYPE_SHADOW_OBJ                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_SHADOW_OBJ)               :
      type==GRAPH_ELEMENT_TYPE_FORM                      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_FORM)                     :
      type==GRAPH_ELEMENT_TYPE_WINDOW                    ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WINDOW)                   :
      //--- WinForms
      type==GRAPH_ELEMENT_TYPE_WF_UNDERLAY               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_UNDERLAY)              :
      type==GRAPH_ELEMENT_TYPE_WF_BASE                   ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BASE)                  :
      //--- Контейнеры
      type==GRAPH_ELEMENT_TYPE_WF_CONTAINER              ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CONTAINER)             :
      type==GRAPH_ELEMENT_TYPE_WF_GROUPBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_GROUPBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_PANEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_PANEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)           :
      type==GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER)       :
      //--- Стандартные элементы управления
      type==GRAPH_ELEMENT_TYPE_WF_COMMON_BASE            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_COMMON_BASE)           :
      type==GRAPH_ELEMENT_TYPE_WF_LABEL                  ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LABEL)                 :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKBOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKBOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON            ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON)           :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON                 ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON)                :
      type==GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX      ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_ELEMENTS_LIST_BOX)     :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX               ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX)              :
      type==GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM          ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM)         :
      type==GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX       ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX)      :
      type==GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX        ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX)       :
      type==GRAPH_ELEMENT_TYPE_WF_TOOLTIP                ? CMessage::Text(MSG_GRAPH_ELEMENT_TYPE_WF_TOOLTIP)               :
      //--- Вспомогательные объекты элементов управления
      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)        :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+

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

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

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

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

public:
//--- (1) Устанавливает, (2) возвращает смещение координаты X относительно базового объекта
   void              SetCoordXRelative(const int value)                                { this.m_shift_coord_x=value;                }
   int               CoordXRelative(void)                                        const { return this.m_shift_coord_x;               }
//--- (1) Устанавливает, (2) возвращает смещение координаты Y относительно базового объекта
   void              SetCoordYRelative(const int value)                                { this.m_shift_coord_y=value;                }
   int               CoordYRelative(void)                                        const { return this.m_shift_coord_y;               }
//--- Устанавливает указатель на родительский элемент в составе связанных объектов текущей группы
   void              SetBase(CGCnvElement *base)                                       { this.m_element_base=base;                  }
   
//--- Обработчик событий
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);

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

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

//--- Устанавливает смещение (1) левого, (2) верхнего, (3) правого, (4) нижнего края активной зоны относительно элемента,
//--- (5) все смещения краёв активной зоны относительно элемента, (6) непрозрачность
   void              SetActiveAreaLeftShift(const int value)   { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_LEFT,fabs(value));       }
   void              SetActiveAreaRightShift(const int value)  { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_RIGHT,fabs(value));      }
   void              SetActiveAreaTopShift(const int value)    { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_TOP,fabs(value));        }
   void              SetActiveAreaBottomShift(const int value) { this.SetProperty(CANV_ELEMENT_PROP_ACT_SHIFT_BOTTOM,fabs(value));     }
   void              SetActiveAreaShift(const int left_shift,const int bottom_shift,const int right_shift,const int top_shift);
   virtual void      SetOpacity(const uchar value,const bool redraw=false);
   
//--- (1) Устанавливает, (2) возвращает текст для Tooltip
   virtual void      SetTooltipText(const string text)         { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_TEXT,text);                }
   virtual string    TooltipText(void)                         { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_TEXT);              }
   
//--- (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);           }


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

//--- Возвращает смещения координат относительно точки привязки прямоугольника по размеру
   void              GetShiftXYbySize(const int width,               //Размер прямоугольника по ширине
                                      const int height,              //Размер прямоугольника по высоте
                                      const ENUM_FRAME_ANCHOR anchor,// Точка привязки прямоугольника, относительно которой будут рассчитаны смещения
                                      int &shift_x,                  // Сюда будет записана X-координата верхнего левого угла прямоугольника
                                      int &shift_y);                 // Сюда будет записана Y-координата верхнего левого угла прямоугольника
                                      
//+------------------------------------------------------------------+
//| Методы рисования предопределённых стандартных изображений        |
//+------------------------------------------------------------------+
//--- Рисует иконку Info
   void              DrawIconInfo(const int coord_x,const int coord_y,const uchar opacity);
//--- Рисует иконку Warning
   void              DrawIconWarning(const int coord_x,const int coord_y,const uchar opacity);
//--- Рисует иконку Error
   void              DrawIconError(const int coord_x,const int coord_y,const uchar opacity);
//--- Рисует стрелку влево
   void              DrawArrowLeft(const int coord_x,const int coord_y,const color clr,const uchar opacity);
//--- Рисует стрелку вправо
   void              DrawArrowRight(const int coord_x,const int coord_y,const color clr,const uchar opacity);
//--- Рисует стрелку вверх
   void              DrawArrowUp(const int coord_x,const int coord_y,const color clr,const uchar opacity);
//--- Рисует стрелку вниз
   void              DrawArrowDown(const int coord_x,const int coord_y,const color clr,const uchar opacity);
  };
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
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_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);                     // Флаг свёрнутости панели 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,25);                         // Минимальный размер панели 2
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY,500);                                  // Задержка отображения подсказки
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY,5000);                                // Длительность отображения подсказки
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,100);                                   // Задержка отображения новой подсказки одного элемента
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS,false);                                  // Отображать подсказку в неактивном окне
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_ICON,CANV_ELEMENT_TOOLTIP_ICON_NONE);                // Значок, отображаемый в подсказке
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,false);                                   // Подсказка в форме "облачка"
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,true);                                    // Угасание при отображении и скрытии подсказки
      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());
     }
  }
//+------------------------------------------------------------------+
//| Защищённый конструктор                                           |
//+------------------------------------------------------------------+
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_SPLIT_CONTAINER_PANEL1_MIN_SIZE,25);                         // Минимальный размер панели 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_COLLAPSED,false);                     // Флаг свёрнутости панели 1
      this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE,25);                         // Минимальный размер панели 2
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY,500);                                  // Задержка отображения подсказки
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY,5000);                                // Длительность отображения подсказки
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,100);                                   // Задержка отображения новой подсказки одного элемента
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS,false);                                  // Отображать подсказку в неактивном окне
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_ICON,CANV_ELEMENT_TOOLTIP_ICON_NONE);                // Значок, отображаемый в подсказке
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,false);                                   // Подсказка в форме "облачка"
      this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,true);                                    // Угасание при отображении и скрытии подсказки
      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());
     }
  }
//+------------------------------------------------------------------+


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

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

//+------------------------------------------------------------------+
//| Рисует иконку Info                                               |
//+------------------------------------------------------------------+
void CGCnvElement::DrawIconInfo(const int coord_x,const int coord_y,const uchar opacity)
  {
   int x=coord_x+8;
   int y=coord_y+8;
   this.DrawCircleFill(x,y,7,C'0x00,0x77,0xD7',opacity);
   this.DrawCircleWu(x,y,7.5,C'0x00,0x3D,0x8C',opacity);
   this.DrawRectangle(x,y-5,x+1,y-4, C'0xFF,0xFF,0xFF',opacity);
   this.DrawRectangle(x,y-2,x+1,y+4,C'0xFF,0xFF,0xFF',opacity);
  }
//+------------------------------------------------------------------+

В метод передаются координаты верхнего левого угла очерчивающего изображение прямоугольника. Далее рассчитываются координаты центральной точки окружности, рисуется залитая окружность, а сверху — окружность со сглаживанием методом Wu. После того, как окружности нарисованы, рисуется значок "i", состоящий из двух прямоугольников так, чтобы первый нарисовал точку размером 2х2, а второй — вертикальную линию размером 2х6. К сожалению, рисовать сглаженные  линии с заданной толщиной (LineThick) не получается из-за странного алгоритма сглаживания, применяемого при рисовании такой линии, приводящего к некрасивому результату. Поэтому, для рисования линий с толщиной в два пикселя мы просто рисуем прямоугольник шириной в два пикселя и нужной высотой. В методе все цвета заранее предопределены так, чтобы изображение выглядело стандартным как в MS Visual Studio.


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

//+------------------------------------------------------------------+
//| Рисует иконку Warning                                            |
//+------------------------------------------------------------------+
void CGCnvElement::DrawIconWarning(const int coord_x,const int coord_y,const uchar opacity)
  {
   int x=coord_x+8;
   int y=coord_y+1;
   this.DrawTriangleFill(x,y,x+8,y+14,x-8,y+14,C'0xFC,0xE1,0x00',opacity);
   this.DrawTriangleWu(x,y,x+8,y+14,x-7,y+14,C'0xFF,0xB9,0x00',opacity);
   this.DrawRectangle(x,y+5,x+1,y+9,  C'0x00,0x00,0x00',opacity);
   this.DrawRectangle(x,y+11,x+1,y+12,C'0x00,0x00,0x00',opacity);
  }
//+------------------------------------------------------------------+

В метод передаются координаты верхнего левого угла очерчивающего изображение прямоугольника. Далее рассчитываются координаты верхней точки треугольника, рисуется залитый треугольник, а сверху — треугольник со сглаживанием методом Wu. У второго треугольника координата Х его вершины смещается на один пиксель вправо по сравнению с залитым первым треугольником. Сделано это для того, чтобы визуально вершина была толще, так как значок внутри треугольников должен быть шириной в два пикселя, и его невозможно расположить ровно по центру треугольника. Поэтому вершину треугольника мы как бы "размазываем" на два пикселя, что сделает расположение значка внутри визуально по центру. После того, как треугольники нарисованы рисуется значок "!", состоящий из двух прямоугольников так, чтобы первый нарисовал вертикальную линию размером 2х6, а второй — точку размером 2х2.

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

//+------------------------------------------------------------------+
//| Рисует иконку Error                                              |
//+------------------------------------------------------------------+
void CGCnvElement::DrawIconError(const int coord_x,const int coord_y,const uchar opacity)
  {
   int x=coord_x+8;
   int y=coord_y+8;
   this.DrawCircleFill(x,y,7,C'0xF0,0x39,0x16',opacity);
   this.DrawCircleWu(x,y,7.5,C'0xA5,0x25,0x12',opacity);
   this.DrawLineWu(x-3,y-3,x+3,y+3,C'0xFF,0xFF,0xFF',opacity);
   this.DrawLineWu(x+3,y-3,x-3,y+3,C'0xFF,0xFF,0xFF',opacity);
  }
//+------------------------------------------------------------------+

Метод рисует две окружности — залитую цветом и очерчивающую её сглаженную, а в центре две сглаженные линии, образующие значок "Х". Цвета также, как и в других иконках, предопределены для соответствия MS Visual Studio.


Методы, рисующие стрелки влево, вправо, вверх и вниз:

//+------------------------------------------------------------------+
//| Рисует стрелку влево                                             |
//+------------------------------------------------------------------+
void CGCnvElement::DrawArrowLeft(const int coord_x,const int coord_y,const color clr,const uchar opacity)
  {
   int x=coord_x;
   int y=coord_y+5;
   this.DrawTriangleFill(x,y,x+3,y-3,x+3,y+3,clr,opacity);
   this.DrawTriangleWu(x,y,x+3,y-3,x+3,y+3,clr,opacity);
  }
//+------------------------------------------------------------------+
//| Рисует стрелку вправо                                            |
//+------------------------------------------------------------------+
void CGCnvElement::DrawArrowRight(const int coord_x,const int coord_y,const color clr,const uchar opacity)
  {
   int x=coord_x;
   int y=coord_y+5;
   this.DrawTriangleFill(x+3,y,x,y+3,x,y-3,clr,opacity);
   this.DrawTriangleWu(x+3,y,x,y+3,x,y-3,clr,opacity);
  }
//+------------------------------------------------------------------+
//| Рисует стрелку вверх                                             |
//+------------------------------------------------------------------+
void CGCnvElement::DrawArrowUp(const int coord_x,const int coord_y,const color clr,const uchar opacity)
  {
   int x=coord_x+5;
   int y=coord_y;
   this.DrawTriangleFill(x,y,x+3,y+3,x-3,y+3,clr,opacity);
   this.DrawTriangleWu(x,y,x+3,y+3,x-3,y+3,clr,opacity);
  }
//+------------------------------------------------------------------+
//| Рисует стрелку вниз                                              |
//+------------------------------------------------------------------+
void CGCnvElement::DrawArrowDown(const int coord_x,const int coord_y,const color clr,const uchar opacity)
  {
   int x=coord_x+5;
   int y=coord_y+3;
   this.DrawTriangleFill(x,y,x+3,y-3,x-3,y-3,clr,opacity);
   this.DrawTriangleWu(x,y,x+3,y-3,x-3,y-3,clr,opacity);
  }
//+------------------------------------------------------------------+

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

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

В файле \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh переименуем методы SetOpacity() и Opacity() и объявим виртуальный метод SetOpacity():

//--- Рисует тень объекта
   void              Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw);
//--- Устанавливает непрозрачность элемента
   virtual void      SetOpacity(const uchar value,const bool redraw=false);

//+------------------------------------------------------------------+
//| Методы упрощённого доступа к свойствам объекта                   |
//+------------------------------------------------------------------+
//--- (1) Устанавливает, (2) возвращает цвет тени
   void              SetColor(const color colour)                             { this.m_color=colour;     }
   color             Color(void)                                        const { return this.m_color;     }
//--- (1) Устанавливает, (2) возвращает непрозрачность рисуемой тени
   void              SetOpacityDraw(const uchar opacity)                      { this.m_opacity=opacity;  }
   uchar             OpacityDraw(void)                                  const { return this.m_opacity;   }
//--- (1) Устанавливает, (2) возвращает размытие тени
   void              SetBlur(const uchar blur)                                { this.m_blur=blur;        }
   uchar             Blur(void)                                         const { return this.m_blur;      }
  };
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(CGCnvElement *main_obj,CGCnvElement *base_obj,
                       const long chart_id,
                       const int subwindow,
                       const string name,
                       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,name,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();
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Рисует форму тени объекта                                        |
//+------------------------------------------------------------------+
void CShadowObj::DrawShadowFigureRect(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();
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Устанавливает непрозрачность элемента                            |
//+------------------------------------------------------------------+
void CShadowObj::SetOpacity(const uchar value,const bool redraw=false)
  {
   CGCnvElement::SetOpacity(0,false);
   this.SetOpacityDraw(value>(uchar)CLR_DEF_SHADOW_OPACITY ? (uchar)CLR_DEF_SHADOW_OPACITY : value);
   this.m_canvas.Update(redraw);
  }
//+------------------------------------------------------------------+

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

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

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


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

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

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

      property==CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_SPLIT_CONTAINER_PANEL2_MIN_SIZE)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_ICON ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_ICON)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON)+
         (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)
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Возвращает описание строкового свойства элемента                 |
//+------------------------------------------------------------------+
string CWinFormBase::GetPropertyDescription(ENUM_CANV_ELEMENT_PROP_STRING property,bool only_prop=false)
  {
   return
     (
      property==CANV_ELEMENT_PROP_NAME_OBJ         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_OBJ)+": \""+this.GetProperty(property)+"\""        :
      property==CANV_ELEMENT_PROP_NAME_RES         ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_NAME_RES)+": \""+this.GetProperty(property)+"\""        :
      property==CANV_ELEMENT_PROP_TEXT             ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TEXT)+": \""+this.GetProperty(property)+"\""            :
      property==CANV_ELEMENT_PROP_DESCRIPTION      ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_DESCRIPTION)+": \""+this.GetProperty(property)+"\""     :
      property==CANV_ELEMENT_PROP_TOOLTIP_TITLE    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_TITLE)+": \""+this.GetProperty(property)+"\""   :
      property==CANV_ELEMENT_PROP_TOOLTIP_TEXT     ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TOOLTIP_TEXT)+": \""+this.GetProperty(property)+"\""    :
      ""
     );
  }
//+------------------------------------------------------------------+


Так как теперь у нас есть методы для рисования стандартных стрелок, добавим их вызов в методы рисования стрелок DrawArrow() в классы объектов-кнопок со стрелками.

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowLeftButton.mqh:

//+------------------------------------------------------------------+
//| Рисует стрелку                                                   |
//+------------------------------------------------------------------+
void CArrowLeftButton::DrawArrow(void)
  {
   CGCnvElement::DrawArrowLeft(5,2,this.ArrowColor(),this.Opacity());
  }
//+------------------------------------------------------------------+


В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowRightButton.mqh:

//+------------------------------------------------------------------+
//| Рисует стрелку                                                   |
//+------------------------------------------------------------------+
void CArrowRightButton::DrawArrow(void)
  {
   CGCnvElement::DrawArrowRight(6,2,this.ArrowColor(),this.Opacity());
  }
//+------------------------------------------------------------------+


В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowUpButton.mqh:

//+------------------------------------------------------------------+
//| Рисует стрелку                                                   |
//+------------------------------------------------------------------+
void CArrowUpButton::DrawArrow(void)
  {
   CGCnvElement::DrawArrowUp(2,5,this.ArrowColor(),this.Opacity());
  }
//+------------------------------------------------------------------+


В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\ArrowDownButton.mqh:

//+------------------------------------------------------------------+
//| Рисует стрелку                                                   |
//+------------------------------------------------------------------+
void CArrowDownButton::DrawArrow(void)
  {
   CGCnvElement::DrawArrowDown(2,6,this.ArrowColor(),this.Opacity());
  }
//+------------------------------------------------------------------+

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

Приступим к созданию объекта-всплывающей подсказки.


Класс WinForms-объекта ToolTip

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

//+------------------------------------------------------------------+
//|                                                      ToolTip.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 "..\Helpers\HintBase.mqh"
//+------------------------------------------------------------------+
//| Класс базового объекта Hint элементов управления WForms          |
//+------------------------------------------------------------------+
class CToolTip : public CHintBase
  {
  }


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

class CToolTip : public CHintBase
  {
private:
//--- Корректирует размеры подсказки по размерам текстов
   void              CorrectSizeByTexts(void);
protected:
   //--- Рисует (1) подсказку, (2) иконку, (3) Info, (4) Warning, (5) Error, (6) User
   virtual void      DrawHint(const int shift);
   void              DrawIcon(void);
   void              DrawIconInfo(void)                  { CGCnvElement::DrawIconInfo(3,1,this.Opacity());                       }
   void              DrawIconWarning(void)               { CGCnvElement::DrawIconWarning(3,1,this.Opacity());                    }
   void              DrawIconError(void)                 { CGCnvElement::DrawIconError(3,1,this.Opacity());                      }
   virtual void      DrawIconUser(void)                  { return; }
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     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);
public:

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

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

public:
//--- (1) Устанавливает, (2) возвращает задержку отображения подсказки
   void              SetInitialDelay(const long delay)   { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY,delay);      }
   long              InitialDelay(void)                  { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_INITIAL_DELAY);     }
   
//--- (1) Устанавливает, (2) возвращает длительность отображения подсказки
   void              SetAutoPopDelay(const long delay)   { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY,delay);     }
   long              AutoPopDelay(void)                  { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_AUTO_POP_DELAY);    }
   
//--- (1) Устанавливает, (2) возвращает задержку отображения новой подсказки одного элемента
   void              SetReshowDelay(const long delay)    { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY,delay);       }
   long              ReshowDelay(void)                   { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_RESHOW_DELAY);      }
   
//--- (1) Устанавливает, (2) возвращает флаг отображения подсказку в неактивном окне
   void              SetShowAlways(const bool flag)      { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS,flag);         }
   bool              ShowAlways(void)                    { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_SHOW_ALWAYS); }
   
//--- (1) Устанавливает, (2) возвращает тип значка, отображаемого в подсказке
   void              SetIcon(const ENUM_CANV_ELEMENT_TOOLTIP_ICON ico)
                       { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_ICON,ico);   }
   ENUM_CANV_ELEMENT_TOOLTIP_ICON Icon(void)
                       { return (ENUM_CANV_ELEMENT_TOOLTIP_ICON)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_ICON); }
   
//--- (1) Устанавливает, (2) возвращает флаг подсказки в форме "облачка"
   void              SetBalloon(const bool flag)         { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON,flag);          }
   bool              Balloon(void)                       { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_IS_BALLOON);  }
   
//--- (1) Устанавливает, (2) возвращает флаг угасания при отображении и скрытии подсказки
   void              SetUseFading(const bool flag)       { this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_USE_FADING,flag);          }
   bool              UseFading(void)                     { return (bool)this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_USE_FADING);  }
   
//--- (1) Устанавливает, (2) возвращает заголовок для Tooltip
   void              SetTitle(const string header);
   string            Title(void)                         { return this.GetProperty(CANV_ELEMENT_PROP_TOOLTIP_TITLE);             }
   
//--- (1,2) Устанавливает текст для Tooltip
   virtual void      SetTooltipText(const string text);
   virtual void      SetText(const string text)          { this.SetTooltipText(text);                                            }
   
//--- Конструктор
                     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);
//--- Показывает элемент
   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);
  };
//+------------------------------------------------------------------+

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

Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна:

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
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();
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
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();
  }
//+------------------------------------------------------------------+

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


Метод инициализации переменных:

//+------------------------------------------------------------------+
//| Инициализирует переменные                                        |
//+------------------------------------------------------------------+
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(500);
   this.SetAutoPopDelay(5000);
   this.SetReshowDelay(100);
   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::Redraw(bool redraw)
  {
//--- Если элемент не должен показываться (скрыт внутри другого элемента управления) - уходим
   if(!this.Displayed() || this.Text()=="")
      return;
//--- Получим объект "Тень"
   CShadowObj *shadow=this.GetShadowObj();
//--- Если стоит флаг перерисовки
   if(redraw)
     {
      //--- полностью перерисовываем объект и запоминаем его новый изначальный вид
      this.Erase(this.m_array_colors_bg,this.Opacity(),this.m_gradient_v,this.m_gradient_c,true);
      this.Done();
      //--- Если у объекта есть тень, и объект "Тень" существует - перерисуем тень
      if(this.IsShadow() && shadow!=NULL)
        {
         //--- сотрём ранее нарисованную тень,
         shadow.Erase();
         //--- запомним относительные координаты тени,
         int x=shadow.CoordXRelative();
         int y=shadow.CoordYRelative();
         //--- перерисуем тень,
         shadow.Draw(0,0,shadow.Blur(),true);
         //--- восстановим относительные координаты тени
         shadow.SetCoordXRelative(x);
         shadow.SetCoordYRelative(y);
        }
     }
//--- иначе - стираем объект и его тень
   else
     {
      CGCnvElement::Erase();
      if(this.IsShadow() && shadow!=NULL)
         shadow.Erase();
     }
  }
//+------------------------------------------------------------------+

Логика метода расписана в комментариях к коду. Здесь практически всё точно так же, как и в одноимённом методе класса базового WinForms-объекта CWinFormBase, за исключением перерисовки привязанных объектов и чарта.


Методы очистки элемента с закрашиванием его цветом фона:

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

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

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

//+------------------------------------------------------------------+
//| Рисует рамку элемента                                            |
//+------------------------------------------------------------------+
void CToolTip::DrawFrame(void)
  {
//--- Если элемент не должен показываться (скрыт внутри другого элемента управления) - уходим
   if(!this.Displayed() || this.Text()=="")
      return;
//--- Рисуем прямоугольник по краям объекта
   this.DrawRectangle(0,0,this.Width()-1,this.Height()-1,this.BorderColor(),this.Opacity());
  }
//+------------------------------------------------------------------+

Логика метода прокомментирована в коде. Отличие от метода родительского класса здесь лишь в проверке необходимости отображения данного элемента.


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

//+------------------------------------------------------------------+
//| Показывает элемент                                               |
//+------------------------------------------------------------------+
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);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Устанавливает текст для Tooltip                                  |
//+------------------------------------------------------------------+
void CToolTip::SetTooltipText(const string text)
  {
   CGCnvElement::SetTooltipText(text);
   CWinFormBase::SetText(text);
   this.CorrectSizeByTexts();
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Устанавливает текст заголовка для Tooltip                        |
//+------------------------------------------------------------------+
void CToolTip::SetTitle(const string header)
  {
   this.SetProperty(CANV_ELEMENT_PROP_TOOLTIP_TITLE,header);
   this.CorrectSizeByTexts();
  }
//+------------------------------------------------------------------+

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


Метод, корректирующий размеры подсказки по размерам текстов:

//+------------------------------------------------------------------+
//| Корректирует размеры подсказки по размерам текстов               |
//+------------------------------------------------------------------+
void CToolTip::CorrectSizeByTexts(void)
  {
//--- Если для объекта не задан текст подсказки - уходим
   if(this.TooltipText()=="" || this.TooltipText()==NULL)
      return;
//--- Объявляем переменные для ширины и высоты объекта
   int w=this.Width();
   int h=this.Height();
//--- Объявляем переменные для ширины и высоты текстов заголовка и подсказки
   int w1=0;
   int h1=0;
   int w2=w;
   int h2=h;
//--- Если текст заголовка задан - получаем и корректируем его размеры
   if(this.Title()!="" && this.Title()!=NULL)
     {
      this.TextSize(this.Title(),w1,h1);
      if(w1<6)
         w1=6;
      if(h1<19)
         h1=19;
     }
//--- Если текст подсказки задан - получаем и корректируем его размеры
   this.TextSize(this.Text(),w2,h2);
   if(w2<6)
      w2=6;
   if(h2<19)
      h2=19;
//--- Рассчитываем итоговые размеры всплывающей подсказки
   w=fmax(w1,w2);
   h=h1+h2;
//--- Устанавливаем размер объекта в соответствии с рассчитанными
   this.Resize(w+12+(this.Icon()>CANV_ELEMENT_TOOLTIP_ICON_NONE && this.Icon()<=CANV_ELEMENT_TOOLTIP_ICON_USER ? 16 : 0),h,false);
  }
//+------------------------------------------------------------------+

Логика метода расписана в комментариях к коду. Вкратце: если для объекта не задан текст всплывающей подсказки, то подсказка не выводится. Независимо от того, задан ли для объекта текст заголовка, или нет. А если подсказка не выводится, то и её размеры менять не нужно. Поэтому в такой ситуации просто уходим из метода. А далее, если текст подсказки установлен, то нам нужно получить размеры текста заголовка и подсказки. Если ширина полученного текста меньше 6, то ширина объекта будет равна 6, если высота меньше 19, то высота объекта должна быть 19. Далее определяем наибольшую ширину из двух текстов и используем её в качестве ширины объекта. А высота объекта равна сумме высот двух текстов. При изменении размеров учитываем отступ текста от левого края в шесть пикселей. Соответсвенно, отступ и справа тоже должен быть равен шести пикселям. Поэтому к рассчитанной ширине добавляем 12 пикселей и проверяем условие наличия иконки. Если иконка должна рисоваться, то добавляем к ширине объекта ширину иконки в 16 пикселей. В итоге получаем корректный размер объекта, где оба текста — зоголовок и описание и иконка, при её наличии, будут смотреться гармонично.


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

//+------------------------------------------------------------------+
//| Рисует подсказку                                                 |
//+------------------------------------------------------------------+
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());
  }
//+------------------------------------------------------------------+

Задаём отступ текста по вертикали в три пикселя, отступ по горизонтали в шесть пикселей. Если должна рисоваться иконка, то к горизонтальному отступу добавляем её ширину в 16 пикселей. Если у подсказки должен быть заголовок, то устанавливаем для фонта "жирный" шрифт, выводим текст заголовка, восстанавливаем толщину шрифта до нормальной и прибавляем к вертикальной координате высоту текста заголовка плюс 4 пикселя. По окончании выводим текст подсказки. Если заголовка нет, то текст подсказки будет выведен на начальной координате Y, а при наличии заголовка — на рассчитанной.


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

//+------------------------------------------------------------------+
//| Рисует иконку                                                    |
//+------------------------------------------------------------------+
void CToolTip::DrawIcon(void)
  {
   switch(this.Icon())
     {
      case CANV_ELEMENT_TOOLTIP_ICON_INFO    :  this.DrawIconInfo();    break;
      case CANV_ELEMENT_TOOLTIP_ICON_WARNING :  this.DrawIconWarning(); break;
      case CANV_ELEMENT_TOOLTIP_ICON_ERROR   :  this.DrawIconError();   break;
      case CANV_ELEMENT_TOOLTIP_ICON_USER    :  this.DrawIconUser();    break;
      //--- Icon None
      default: break;
     }
  }
//+------------------------------------------------------------------+

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

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

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


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

//--- Обновляет координаты (сдвигает канвас)
   virtual bool      Move(const int x,const int y,const bool redraw=false);
//--- Устанавливает приоритет графического объекта на получение события нажатия мышки на графике
   virtual bool      SetZorder(const long value,const bool only_prop);
//--- Устанавливает объект выше всех
   virtual void      BringToTop(void);
//--- Устанавливает флаг отображения не скрытого элемента управления
   virtual void      SetDisplayed(const bool flag);
//--- Устанавливает непрозрачность элемента
   virtual void      SetOpacity(const uchar value,const bool redraw=false);

//--- Обработчик событий
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);


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

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

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

//--- Рисует тень объекта
   void              DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR);


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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CForm::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(type,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));
   else
     {
      element.SetMovable(movable);
      element.SetCoordXRelative(element.CoordX()-this.CoordX());
      element.SetCoordYRelative(element.CoordY()-this.CoordY());
     }
   return element;
  }
//+------------------------------------------------------------------+

Если объект не создан, то выводится об этом запись в журнал. А далее шло обращение к указателю несозданного объекта, что могло привести к критической ошибке. Поэтому блок установки параметров созданного объекта заключим в else {}.

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


Метод, создающий присоединённый объект-ToolTip:

//+------------------------------------------------------------------+
//| Создаёт присоединённый объект-ToolTip                            |
//+------------------------------------------------------------------+
bool CForm::AddTooltip(CForm *tooltip)
  {
//--- Если передан указатель на пустой объект или тип объекта не равен Tooltip - сообщаем об ошибке и возвращаем false
   if(tooltip==NULL || tooltip.TypeGraphElement()!=GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
     {
      CMessage::ToLog(DFUN,MSG_GRAPH_ELM_COLLECTION_ERR_EMPTY_OBJECT);
      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.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()+1,this.CoordY()+1))
     {
      tooltip.SetCoordXRelative(tooltip.CoordX()-this.CoordX());
      tooltip.SetCoordYRelative(tooltip.CoordY()-this.CoordY());
     }
//--- Устанавливаем этот объект как базовый для объекта Tooltip и возвращаем true
   tooltip.SetBase(this.GetObject());
   return true;
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Возвращает присоединённый объект-ToolTip                         |
//+------------------------------------------------------------------+
CForm *CForm::GetTooltip(void)
  {
   CForm *obj=NULL;
   for(int i=this.ElementsTotal()-1;i>WRONG_VALUE;i--)
     {
      obj=this.GetElement(i);
      if(obj!=NULL && obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
         break;
     }
   return obj;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Возвращает присоединённый объект-ToolTip по описанию             |
//+------------------------------------------------------------------+
CForm *CForm::GetTooltipByDescription(const string descript)
  {
   CForm *obj=NULL;
   for(int i=this.ElementsTotal()-1;i>WRONG_VALUE;i--)
     {
      obj=this.GetElement(i);
      if(obj!=NULL && obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP && obj.Description()==descript)
         break;
     }
   return obj;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает текст для Tooltip                                  |
//+------------------------------------------------------------------+
void CForm::SetTooltipText(const string text)
  {
   CGCnvElement::SetTooltipText(text);
   CForm *tooltip=this.GetTooltip();
   if(tooltip!=NULL)
      tooltip.SetTooltipText(text);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Устанавливает флаг отображения не скрытого элемента управления   |
//+------------------------------------------------------------------+
void CForm::SetDisplayed(const bool flag)
  {
//--- Устанавливаем объекту флаг отображения
   CGCnvElement::SetDisplayed(flag);
//--- Если стоит флаг использования тени и объект тени создан -
//--- устанавливаем флаг отображения объекту тени
   if(this.m_shadow && this.m_shadow_obj!=NULL)
      this.m_shadow_obj.SetDisplayed(flag);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Устанавливает непрозрачность элемента                            |
//+------------------------------------------------------------------+
void CForm::SetOpacity(const uchar value,const bool redraw=false)
  {
//--- Устанавливаем объекту значение непрозрачности
   CGCnvElement::SetOpacity(value,redraw);
//--- Если стоит флаг использования тени и объект тени создан -
//--- устанавливаем значение непрозрачности объекту тени
   if(this.m_shadow && this.m_shadow_obj!=NULL)
      this.m_shadow_obj.SetOpacity(value,redraw);
  }
//+------------------------------------------------------------------+

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


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

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh впишем подключение файла класса CToolTip:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#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"
//+------------------------------------------------------------------+
//| Класс объекта Panel элементов управления WForms                  |
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
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;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Устанавливает параметры присоединённому объекту                  |
//+------------------------------------------------------------------+
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"

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

      //--- Для 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;
      default:
        break;
     }
   obj.Crop();
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CGroupBox::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_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;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


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

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

//--- Возвращает список (1) заголовков, (2) полей вкладок, указатель на объекты кнопки (3) "вверх-вниз", (4) "влево-вправо"
   CArrayObj        *GetListHeaders(void)          { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);        }
   CArrayObj        *GetListFields(void)           { return this.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);         }
   CArrowUpDownBox  *GetArrUpDownBox(void)         { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0); }
   CArrowLeftRightBox *GetArrLeftRightBox(void)    { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,0); }

//--- Возвращает указатель на кнопку со стрелкой (1) вверх, (2) вниз, (3) влево, (4) вправо
   CArrowUpButton   *GetArrowUpButton(void);
   CArrowDownButton *GetArrowDownButton(void);
   CArrowLeftButton *GetArrowLeftButton(void);
   CArrowRightButton*GetArrowRightButton(void);
   
//--- Возвращает указатель на (1) последний в списке, (2) первый видимый заголовок вкладки
   CTabHeader       *GetLastHeader(void)           { return this.GetTabHeader(this.TabPages()-1);                                }
   CTabHeader       *GetFirstVisibleHeader(void);
//--- Устанавливает вкладку выбранной


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

//--- Устанавливает вкладку, указанную по индексу, выбранной/не выбранной
   void              Select(const int index,const bool flag);
//--- Возвращает (1) номер, (2) указатель на выбранную вкладку
   int               SelectedTabPageNum(void)      const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);}
   CWinFormBase     *SelectedTabPage(void)               { return this.GetTabField(this.SelectedTabPageNum());             }
   
//--- Добавляет Tooltip к кнопке со стрелкой (1) вверх, (2) вниз, (3) влево, (4) вправо
   bool              AddTooltipToArrowUpButton(CForm *tooltip);
   bool              AddTooltipToArrowDownButton(CForm *tooltip);
   bool              AddTooltipToArrowLeftButton(CForm *tooltip);
   bool              AddTooltipToArrowRightButton(CForm *tooltip);
   
//--- Устанавливает объект выше всех
   virtual void      BringToTop(void);
//--- Показывает элемент управления
   virtual void      Show(void);
//--- Обработчик событий


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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CTabControl::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_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_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_TOOLTIP              : element=new CToolTip(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;
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Возвращает указатель на кнопку со стрелкой вверх                 |
//+------------------------------------------------------------------+
CArrowUpButton *CTabControl::GetArrowUpButton(void)
  {
   CArrowUpDownBox *box=this.GetArrUpDownBox();
   return(box!=NULL ? box.GetArrowUpButton() : NULL);
  }
//+------------------------------------------------------------------+
//| Возвращает указатель на кнопку со стрелкой вниз                  |
//+------------------------------------------------------------------+
CArrowDownButton *CTabControl::GetArrowDownButton(void)
  {
   CArrowUpDownBox *box=this.GetArrUpDownBox();
   return(box!=NULL ? box.GetArrowDownButton() : NULL);
  }
//+------------------------------------------------------------------+
//| Возвращает указатель на кнопку со стрелкой влево                 |
//+------------------------------------------------------------------+
CArrowLeftButton *CTabControl::GetArrowLeftButton(void)
  {
   CArrowLeftRightBox *box=this.GetArrLeftRightBox();
   return(box!=NULL ? box.GetArrowLeftButton() : NULL);
  }
//+------------------------------------------------------------------+
//| Возвращает указатель на кнопку со стрелкой вправо                |
//+------------------------------------------------------------------+
CArrowRightButton *CTabControl::GetArrowRightButton(void)
  {
   CArrowLeftRightBox *box=this.GetArrLeftRightBox();
   return(box!=NULL ? box.GetArrowRightButton() : NULL);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Добавляет ToolTip к кнопке со стрелкой вверх                     |
//+------------------------------------------------------------------+
bool CTabControl::AddTooltipToArrowUpButton(CForm *tooltip)
  {
   CArrowUpButton *butt=this.GetArrowUpButton();
   return(butt!=NULL ? butt.AddTooltip(tooltip) : false);
  }
//+------------------------------------------------------------------+
//| Добавляет ToolTip к кнопке со стрелкой вниз                      |
//+------------------------------------------------------------------+
bool CTabControl::AddTooltipToArrowDownButton(CForm *tooltip)
  {
   CArrowDownButton *butt=this.GetArrowDownButton();
   return(butt!=NULL ? butt.AddTooltip(tooltip) : false);
  }
//+------------------------------------------------------------------+
//| Добавляет ToolTip к кнопке со стрелкой влево                     |
//+------------------------------------------------------------------+
bool CTabControl::AddTooltipToArrowLeftButton(CForm *tooltip)
  {
   CArrowLeftButton *butt=this.GetArrowLeftButton();
   return(butt!=NULL ? butt.AddTooltip(tooltip) : false);
  }
//+------------------------------------------------------------------+
//| Добавляет ToolTip к кнопке со стрелкой вправо                    |
//+------------------------------------------------------------------+
bool CTabControl::AddTooltipToArrowRightButton(CForm *tooltip)
  {
   CArrowRightButton *butt=this.GetArrowRightButton();
   return(butt!=NULL ? butt.AddTooltip(tooltip) : false);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CTabField::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_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;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+


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

//--- Возвращает указатель на разделитель
   CSplitter        *GetSplitter(void)                         { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_SPLITTER,0);                      }
//--- Возвращает указатель на объект-подсказку (1) "Смещение влево", (1) "Смещение вправо", (1) "Смещение вверх", (1) "Смещение вниз"
   CHintMoveLeft    *GetHintMoveLeft(void)                     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0);                }
   CHintMoveRight   *GetHintMoveRight(void)                    { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0);               }
   CHintMoveUp      *GetHintMoveUp(void)                       { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0);                  }
   CHintMoveDown    *GetHintMoveDown(void)                     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0);                }
//--- Добавляет Tooltip к разделителю
   bool              AddTooltipToSplitter(CForm *tooltip);

//--- (1) устанавливает, (2) возвращает минимально-возможный размер панели 1 и 2


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

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


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

//+------------------------------------------------------------------+
//| Добавляет ToolTip к разделителю                                  |
//+------------------------------------------------------------------+
bool CSplitContainer::AddTooltipToSplitter(CForm *tooltip)
  {
   CSplitter *obj=this.GetSplitter();
   return(obj!=NULL ? obj.AddTooltip(tooltip) : false);
  }
//+------------------------------------------------------------------+

Здесь: получаем указатель на объект-разделитель и возвращаем результат добавления указателя на объект-подсказку в объект-разделитель.


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

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

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

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

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

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


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

//+------------------------------------------------------------------+
//| Ищет объекты взаимодействия                                      |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::SearchInteractObj(CForm *form,const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Если передан не пустой указатель
   if(form!=NULL)
     {
      //--- Создадим список объектов взаимодействия
      int total=form.CreateListInteractObj();
      //--- В цикле по созданному списку
      for(int i=total-1;i>WRONG_VALUE;i--)
        {
         //--- получаем очередной объект-форму
         CForm *obj=form.GetInteractForm(i);
         //--- Если объект получен, но он не видимый, или не активен, или не должен отображаться - пропускаем его
         if(obj==NULL || !obj.IsVisible() || !obj.Enabled() || !obj.Displayed())
            continue;
         
         //--- Если это Tooltip - пока пропускаем такой объект
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
           {
            continue;
           }
         
         //--- Если объект-форма - элемент управления TabControl, то возвращаем выбранную вкладку, находящуюся под курсором
         if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL)
           {
            //---...
            //---...

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


Метод, обрабатывающий элементы ToolTip:

//+------------------------------------------------------------------+
//| Обработка элементов ToolTip                                      |
//+------------------------------------------------------------------+
void CGraphElementsCollection::TooltipProcessing(CForm *form,CForm *tooltip,CForm *tooltip_prev)
  {
//--- Получаем ширину и высоту графика
   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.IsVisible())
     {
      //--- Если объект-подсказка смещён на полученные координаты курсора
      if(tooltip.Move(x,y))
        {
         //--- Получаем объект, к которому назначен объект-подсказка
         CForm *base=tooltip.GetBase();
         //--- Если объект получен - устанавливаем новые относительные координаты подсказки
         if(base!=NULL)
           {
            tooltip.SetCoordXRelative(tooltip.CoordX()-base.CoordX());
            tooltip.SetCoordYRelative(tooltip.CoordY()-base.CoordY());
           }
        }
     }
//--- Устанавливаем подсказке флаг отрисовки, полную непрозрачность и отображаем объект
   tooltip.SetDisplayed(true);
   tooltip.SetOpacity(255);
   tooltip.Show();
//--- Если прошлый объект-подсказка существует и его имя не равно имени текущего объекта (это но один и тот же объект)
   if(tooltip_prev!=NULL && tooltip_prev.Name()!=tooltip.Name())
     {
      //--- Устанавливаем флаг запрета отображения, делаем объект полностью прозрачным и скрываем его
      tooltip_prev.SetDisplayed(false);
      tooltip.SetOpacity(0);
      tooltip_prev.Hide();
     }
  }
//+------------------------------------------------------------------+

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


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

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

            //--- Если флаг перемещения снят
            else
              {
               //--- Получаем объект ToolTip, назначенный форме, и объявляем предыдущий ToolTip
               CForm *tooltip=form.GetTooltip();
               static CForm *tooltip_prev=NULL;
               //--- Если объект ToolTip получен
               if(tooltip!=NULL)
                 {
                  //--- и если его тип действительно ToolTip (во избежание incorrect casting, которое иногда получаем, если не проверить тип)
                  if(tooltip.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
                    {
                     //--- Получаем базовый объект, записанный в объекте ToolTip
                     CForm *base=tooltip.GetBase();
                     //--- Если базовый объект получен
                     if(base!=NULL)
                       {
                        //--- и если имя базового объекта такое же, как и имя текущей формы (этот ToolTip привязан к этой форме)
                        if(base.Name()==form.Name())
                          {
                           //--- Обработаем полученный и прошлый ToolTip и запишем в переменную текущий ToolTip как прошлый
                           this.TooltipProcessing(form,tooltip,tooltip_prev);
                           tooltip_prev=tooltip;
                          }
                       }
                    }
                 }
               //--- Если к объекту не привязан ToolTip
               else
                 {
                  //--- Если прошлый ToolTip существует
                  if(tooltip_prev!=NULL)
                    {
                     //--- и если его тип действительно ToolTip (во избежание incorrect casting, которое иногда получаем, если не проверить тип)
                     if(tooltip_prev.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_TOOLTIP)
                       {
                        //--- Устанавливаем флаг неотображения объекта, делаем его полностью прозрачным и скрываем
                        tooltip_prev.SetDisplayed(false);
                        tooltip_prev.SetOpacity(0,false);
                        tooltip_prev.Hide();
                       }
                    }
                 }
               //--- Если в 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);
              }
           }
        }
      
      //--- Если курсор не над формой
      if(form==NULL)
        {


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

Логика этого блока кода расписана в комментариях. Очень интересно, но после обновления терминала (как мне показалось) не всегда верно отрабатываются логические условия, в которых идёт одновременно проверка указателя на не NULL и какого-либо свойства проверяемого объекта. Поэтому здесь условия, которые можно прописать одной строкой, разбиты на блоки в скобках { }. Я могу и ошибаться, но в любом случае этот блок кода будет перерабатываться при дальнейшей разработке класса объекта-всплывающей подсказки. В данном случае код лишь позволяет найти объект, для которого задана подсказка, и отобразить эту подсказку, скрыв при этом предыдущую. Т.е., никаких задержек при отображении подсказки тут нет, равно как нет и плавного её появления. Это всё будем делать в последующих статьях.

Это всё, что нам сегодня нужно было сделать. Протестируем что у нас получилось.


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

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

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

//--- input parameters
sinput   bool                          InpMovable           =  true;                   // Panel Movable flag
sinput   ENUM_INPUT_YES_NO             InpAutoSize          =  INPUT_YES;              // Panel Autosize
sinput   ENUM_AUTO_SIZE_MODE           InpAutoSizeMode      =  AUTO_SIZE_MODE_GROW;    // Panel Autosize mode
sinput   ENUM_BORDER_STYLE             InpFrameStyle        =  BORDER_STYLE_SIMPLE;    // Label border style
sinput   ENUM_ANCHOR_POINT             InpTextAlign         =  ANCHOR_CENTER;          // Label text align
sinput   ENUM_INPUT_YES_NO             InpTextAutoSize      =  INPUT_NO;               // Label autosize
sinput   ENUM_ANCHOR_POINT             InpCheckAlign        =  ANCHOR_LEFT;            // Check flag align
sinput   ENUM_ANCHOR_POINT             InpCheckTextAlign    =  ANCHOR_LEFT;            // Check label text align
sinput   ENUM_CHEK_STATE               InpCheckState        =  CHEK_STATE_UNCHECKED;   // Check flag state
sinput   ENUM_INPUT_YES_NO             InpCheckAutoSize     =  INPUT_YES;              // CheckBox autosize
sinput   ENUM_BORDER_STYLE             InpCheckFrameStyle   =  BORDER_STYLE_NONE;      // CheckBox border style
sinput   ENUM_ANCHOR_POINT             InpButtonTextAlign   =  ANCHOR_CENTER;          // Button text align
sinput   ENUM_INPUT_YES_NO             InpButtonAutoSize    =  INPUT_YES;              // Button autosize
sinput   ENUM_AUTO_SIZE_MODE           InpButtonAutoSizeMode=  AUTO_SIZE_MODE_GROW;    // Button Autosize mode
sinput   ENUM_BORDER_STYLE             InpButtonFrameStyle  =  BORDER_STYLE_NONE;      // Button border style
sinput   bool                          InpButtonToggle      =  true ;                  // Button toggle flag
sinput   bool                          InpButtListMSelect   =  false;                  // ButtonListBox Button MultiSelect flag
sinput   bool                          InpListBoxMColumn    =  true;                   // ListBox MultiColumn flag
sinput   bool                          InpTabCtrlMultiline  =  false;                   // Tab Control Multiline flag
sinput   ENUM_ELEMENT_ALIGNMENT        InpHeaderAlignment   =  ELEMENT_ALIGNMENT_TOP;  // TabHeader Alignment
sinput   ENUM_ELEMENT_TAB_SIZE_MODE    InpTabPageSizeMode   =  ELEMENT_TAB_SIZE_MODE_FILL; // TabHeader Size Mode
sinput   int                           InpTabControlX       =  10;                     // TabControl X coord
sinput   int                           InpTabControlY       =  20;                     // TabControl Y coord
sinput   ENUM_CANV_ELEMENT_TOOLTIP_ICON InpTooltipIcon      =  CANV_ELEMENT_TOOLTIP_ICON_NONE;  // Tooltip Icon
sinput   string                        InpTooltipTitle      =  "";                     // Tooltip Title
//--- global variables


После того, как будет создан элемент управления TabControl, создадим два объекта-вплывающие подсказки и назначим их для двух кнопок прокрутки строки заголовков влево-вправо:

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

         //--- Создадим элемент управления TabControl
         pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false);
         CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
         if(tc!=NULL)
           {
            tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
            tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
            tc.SetMultiline(InpTabCtrlMultiline);
            tc.SetHeaderPadding(6,0);
            tc.CreateTabPages(15,0,56,20,TextByLanguage("Вкладка","TabPage"));
            
            //--- Создадим Tooltip для кнопки "Влево"
            tc.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,0,0,10,10,clrNONE,0,false,false);
            CToolTip *tooltip=tc.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,0);
            if(tooltip!=NULL)
              {
               tooltip.SetDescription("Left Button Tooltip");
               tooltip.SetIcon(InpTooltipIcon);
               tooltip.SetTitle(InpTooltipTitle);
               tooltip.SetTooltipText(TextByLanguage("Нажмите для прокрутки заголовков вправо","Click to scroll headings to the right"));
               tc.AddTooltipToArrowLeftButton(tooltip);
              }
            //--- Создадим Tooltip для кнопки "Вправо"
            tc.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,0,0,10,10,clrNONE,0,false,false);
            tooltip=tc.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TOOLTIP,1);
            if(tooltip!=NULL)
              {
               tooltip.SetDescription("Right Button Tooltip");
               tooltip.SetIcon(ENUM_CANV_ELEMENT_TOOLTIP_ICON(InpTooltipIcon+1));
               tooltip.SetTitle(InpTooltipTitle);
               tooltip.SetTooltipText(TextByLanguage("Нажмите для прокрутки заголовков влево","Click to scroll headings to the left"));
               tc.AddTooltipToArrowRightButton(tooltip);
              }
              
            //--- Создадим на каждой вкладке текстовую метку с описанием вкладки
            for(int j=0;j<tc.TabPages();j++)
              {
                //---...
                //---...

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

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


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


Что дальше

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

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

К содержанию

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

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

Прикрепленные файлы |
MQL5.zip (4650.14 KB)
Машинное обучение и Data Science. Нейросети (Часть 02): архитектура нейронных сетей с прямой связью Машинное обучение и Data Science. Нейросети (Часть 02): архитектура нейронных сетей с прямой связью
В предыдущей статье мы начали изучать нейросети с прямой связью, однако остались неразобранными некоторые моменты. Один из них — проектирование архитектуры. Поэтому в этой статье мы рассмотрим, как спроектировать гибкую нейронную сеть с учетом входных данных, количества скрытых слоев и узлов для каждой сети.
Адаптивные индикаторы Адаптивные индикаторы
В этой статье мы рассмотрим несколько возможных подходов к созданию адаптивных индикаторов. Адаптивные индикаторы отличаются наличием обратной связи между значениями входных и выходного сигналов. Эта связь позволяет индикатору самостоятельно подстраиваться на оптимальную обработку значений финансового временного ряда.
Магия временных торговых интервалов с инструментом Frames Analyzer Магия временных торговых интервалов с инструментом Frames Analyzer
Что такое Frames Analyzer? Это подключаемый модуль к любому торговому эксперту для анализа фреймов оптимизации во время оптимизации параметров в тестере стратегий, а также вне тестера посредством чтения MQD-файла или базы данных, которая создаётся сразу после оптимизации параметров. Вы сможете делиться этими результатами оптимизации с другими пользователями, у которых есть инструмент Frames Analyzer, чтобы обсудить полученные результаты оптимизации вместе.
Популяционные алгоритмы оптимизации: Муравьиная Колония (Ant Colony Optimization - ACO) Популяционные алгоритмы оптимизации: Муравьиная Колония (Ant Colony Optimization - ACO)
В этот раз разберём алгоритм оптимизации Муравьиная Колония. Алгоритм очень интересный и неоднозначный. Попытка создания нового типа ACO.