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

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

Содержание


Концепция

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

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

Помимо доработки конструкторов объектов-графических элементов мы сегодня создадим несколько новых WinForms-объектов. При работе с элементами управления в Windows-приложениях можно заметить, как курсор мышки изменяет свой вид, когда он оказывается в области объекта. Изменение указывает на то, что взаимодействие с этой областью может привести, например, к изменению внешнего вида объекта. Также это может указывать на возможность в этой области производить какое-то действие, например, использовать в этой области функционал drag-n-drop.

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

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

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

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


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

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

#define CLR_DEF_CONTROL_SPLIT_CONTAINER_BACK_COLOR    (C'0xF0,0xF0,0xF0')  // Цвет фона элемента управления SplitContainer
#define CLR_DEF_CONTROL_SPLIT_CONTAINER_MOUSE_DOWN    (C'0xF0,0xF0,0xF0')  // Цвет фона элемента управления SplitContainer при нажатии мышки на элемент управления
#define CLR_DEF_CONTROL_SPLIT_CONTAINER_MOUSE_OVER    (C'0xF0,0xF0,0xF0')  // Цвет фона элемента управления SplitContainer при наведении мышки на элемент управления
#define CLR_DEF_CONTROL_SPLIT_CONTAINER_BORDER_COLOR  (C'0x65,0x65,0x65')  // Цвет рамки элемента управления SplitContainer

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

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

#define DEF_FONT                       ("Calibri")                // Шрифт по умолчанию
#define DEF_FONT_SIZE                  (8)                        // Размер шрифта по умолчанию
#define DEF_CHECK_SIZE                 (12)                       // Размер флажка проверки по умолчанию
#define DEF_ARROW_BUTTON_SIZE          (15)                       // Размер кнопки со стрелкой по умолчанию
#define OUTER_AREA_SIZE                (16)                       // Размер одной стороны внешней области вокруг рабочего пространства формы
#define DEF_FRAME_WIDTH_SIZE           (3)                        // Ширина рамки формы/панели/окна по умолчанию
#define DEF_HINT_ICON_SIZE             (11)                       // Размер стороны объекта-подсказки
//--- Параметры графических объектов
#define PROGRAM_OBJ_MAX_ID             (10000)                    // Максимальное значение идентификатора графического объекта, принадлежащего программе
#define CTRL_POINT_RADIUS              (5)                        // Радиус контрольной точки на форме управления опорными точками графического объекта
#define CTRL_POINT_COLOR               (clrDodgerBlue)            // Цвет контрольной точки на форме управления опорными точками графического объекта
#define CTRL_FORM_SIZE                 (40)                       // Размер формы контрольной точки управления опорными точками графического объекта
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Список типов графических элементов                               |
//+------------------------------------------------------------------+
enum ENUM_GRAPH_ELEMENT_TYPE
  {
   GRAPH_ELEMENT_TYPE_STANDARD,                       // Стандартный графический объект
   GRAPH_ELEMENT_TYPE_STANDARD_EXTENDED,              // Расширенный стандартный графический объект

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

   GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX,            // Windows Forms CheckedListBox
   GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX,             // Windows Forms ButtonListBox
   //--- Вспомогательные элементы WinForms-объектов
   GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM,               // Windows Forms ListBoxItem
   GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,                  // Windows Forms TabHeader
   GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,                   // Windows Forms TabField
   GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,       // Windows Forms SplitContainerPanel
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,                // Windows Forms ArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,             // Windows Forms UpArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,           // Windows Forms DownArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,           // Windows Forms LeftArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,          // Windows Forms RightArrowButton
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,        // Windows Forms UpDownArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,        // Windows Forms LeftRightArrowButtonsBox
   GRAPH_ELEMENT_TYPE_WF_SPLITTER,                    // Windows Forms Splitter
   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
  };
//+------------------------------------------------------------------+


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

   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,    // Элемент управления UpDownArrowBox
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,    // Элемент управления LeftRightArrowBox
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_BASE,               // Элемент управления HintBase
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,          // Элемент управления HintMoveLeft
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,         // Элемент управления HintMoveLeft
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,            // Элемент управления HintMoveLeft
   MSG_GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,          // Элемент управления HintMoveLeft
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Графический объект принадлежит программе
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Графический объект не принадлежит программе

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

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


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

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


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

В файле \MQL5\Include\DoEasy\Objects\Graph\GCnvElement.mqh уберём методы для установки родительских объектов (мы же их теперь будем передавать прямо в конструктор класса), оставив только методы для возврата указателей. Метод IsBase() переименуем в IsDependent():

//--- (1) Устанавливает, (2) возвращает изначальное смещение координаты (1) X, (2) Y относительно базового объекта
   void              SetCoordXRelativeInit(const int value)                            { this.m_init_relative_x=value;              }
   void              SetCoordYRelativeInit(const int value)                            { this.m_init_relative_y=value;              }
   int               CoordXRelativeInit(void)                                    const { return this.m_init_relative_x;             }
   int               CoordYRelativeInit(void)                                    const { return this.m_init_relative_y;             }
   
//--- Возвращает указатель на родительский элемент в составе связанных объектов текущей группы
   CGCnvElement     *GetBase(void)                                                     { return this.m_element_base;                }
//--- Возвращает указатель на родительский элемент в составе всех групп связанных объектов
   CGCnvElement     *GetMain(void)     { return(this.m_element_main==NULL ? this.GetObject() : this.m_element_main);                }

//--- Возвращает флаг, что объект является (1) главным, (2) базовым
   bool              IsMain(void)                                                      { return this.m_element_main==NULL;          }
   bool              IsDependent(void)                                                 { return this.m_element_base!=NULL;          }
//--- Возвращает идентификатор (1) главного, (2) базового объекта
   int               GetMainID(void)
                       {
                        if(this.IsMain())
                           return this.ID();
                        CGCnvElement *main=this.GetMain();
                        return(main!=NULL ? main.ID() : WRONG_VALUE);
                       }
   int               GetBaseID(void)
                       {
                        if(!this.IsDependent())
                           return this.ID();
                        CGCnvElement *base=this.GetBase();
                        return(base!=NULL ? base.ID() : WRONG_VALUE);
                       }

Так как наименование метода "IsBase" прямо говорит о том, что проверяется "базовый ли это объект" для какого-либо другого объекта, то это не совсем правильно. Дело в том, что если значение переменной m_element_base равно NULL, то это говорит о том, что у этого объекта нет базового — он не прикреплён ни к какому другому объекту. Но сам он может быть базовым объектом для других (если они созданы внутри него как прикреплённые объекты), а может и не быть базовым — если к нему не прикреплён ни один графический элемент.

Таким образом мы видим, что такая постановка вопроса (Is Base) некорректна. Но вот узнать, что этот объект является зависимым (прикреплённым к родительскому) мы как раз-таки можем — если m_element_base равно NULL, то объект ни к какому другому не прикреплён, т.е. является независимым, в ином случае — объект зависим от своего родительского базового объекта. Поэтому мы и переименовали метод IsBase (это базовый?) на метод IsDependent (это зависимый?) и изменили логику определения зависимости объекта от другого — возвращается флаг того, что m_element_base не равен NULL.
И, соответственно, изменились и проверки на базовый объект. Теперь проверяется флаг самостоятельности объекта, как в этом методе:

   int               GetBaseID(void)
                       {
                        if(!this.IsDependent())
                           return this.ID();
                        CGCnvElement *base=this.GetBase();
                        return(base!=NULL ? base.ID() : WRONG_VALUE);
                       }

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


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

protected:
//--- Защищённый конструктор
                     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);
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;               }
   
//--- Обработчик событий
   virtual void      OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Параметрический конструктор
                     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);
//--- Конструктор по умолчанию
                     CGCnvElement() : m_shadow(false),m_chart_color_bg((color)::ChartGetInteger(::ChartID(),CHART_COLOR_BACKGROUND))
                        {
                         this.m_type=OBJECT_DE_TYPE_GELEMENT;
                         this.m_element_main=NULL;
                         this.m_element_base=NULL;
                         this.m_shift_coord_x=0;
                         this.m_shift_coord_y=0;
                        }
//--- Деструктор
                    ~CGCnvElement()
                        { this.m_canvas.Destroy();             }
     
//+------------------------------------------------------------------+

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

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

   this.m_element_main=NULL;
   this.m_element_base=NULL;

Теперь мы сюда будем вписывать переданные в формальных параметрах значения:

//+------------------------------------------------------------------+
//| Параметрический конструктор                                      |
//+------------------------------------------------------------------+
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);
   //---...
   //---...

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


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

//+------------------------------------------------------------------+
//| Обрезает изображение, очерченное рассчитываемой                  |
//| прямоугольной областью видимости                                 |
//+------------------------------------------------------------------+
void CGCnvElement::Crop(void)
  {
//--- Получаем указатель на базовый объект
   CGCnvElement *base=this.GetBase();
//--- Если у объекта нет базового, к которому он прикреплён, то и обрезать скрытые области не нужно - уходим
   if(!this.IsDependent())
      return;
//--- Задаём начальные координаты и размеры области видимости во весь объект
//---...
//---...


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

//--- Рисует форму тени объекта
   void              DrawShadowFigureRect(const int w,const int h);

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

//--- Поддерживаемые свойства объекта (1) целочисленные, (2) строковые


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

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

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


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

protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CForm(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:                     
//--- Конструкторы
                     CForm(CGCnvElement *main_obj,CGCnvElement *base_obj,
                           const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h);
                     CForm() { this.m_type=OBJECT_DE_TYPE_GFORM; this.Initialize(); }
//--- Деструктор
                    ~CForm();

...

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CForm::CForm(const ENUM_GRAPH_ELEMENT_TYPE type,
             CGCnvElement *main_obj,CGCnvElement *base_obj,
             const long chart_id,
             const int subwindow,
             const string descript,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(type,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CForm::CForm(CGCnvElement *main_obj,CGCnvElement *base_obj,
             const long chart_id,
             const int subwindow,
             const string descript,
             const int x,
             const int y,
             const int w,
             const int h) : CGCnvElement(GRAPH_ELEMENT_TYPE_FORM,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.m_type=OBJECT_DE_TYPE_GFORM; 
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+


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

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

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


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

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//| и добавляет его в список присоединённых объектов                 |
//+------------------------------------------------------------------+
CGCnvElement *CForm::CreateAndAddNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                            const int x,
                                            const int y,
                                            const int w,
                                            const int h,
                                            const color colour,
                                            const uchar opacity,
                                            const bool activity)
  {
//--- Если тип создаваемого графического элемента меньше, чем "элемент" - сообщаем об этом и возвращаем false
   if(element_type<GRAPH_ELEMENT_TYPE_ELEMENT)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_NOT_INTENDED),::StringSubstr(::EnumToString(element_type),19));
      return NULL;
     }
//--- Задаём номер элемента в списке
   int num=this.m_list_elements.Total();
//--- Создаём описание графического элемента по умолчанию
   string descript=TypeGraphElementAsString(element_type);
//--- Получаем экранные координаты объекта относительно системы координат базового объекта
   int elm_x=x;
   int elm_y=y;
   this.GetCoords(elm_x,elm_y);
//--- Создаём новый графический элемент
   CGCnvElement *obj=this.CreateNewGObject(element_type,num,descript,elm_x,elm_y,w,h,colour,opacity,false,activity);
   if(obj==NULL)
      return NULL;
//--- и добавляем его в список привязанных графических элементов
   if(!this.AddNewElement(obj,elm_x,elm_y))
     {
      delete obj;
      return NULL;
     }
//--- Устанавливаем минимальные свойства привязанному графическому элементу
   obj.SetBackgroundColor(colour,true);
   obj.SetOpacity(opacity);
   obj.SetActive(activity);
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetObject());
   obj.SetID(this.GetMaxIDAll()+1);
   obj.SetNumber(num);
   obj.SetCoordXRelative(obj.CoordX()-this.CoordX());
   obj.SetCoordYRelative(obj.CoordY()-this.CoordY());
   obj.SetZorder(this.Zorder(),false);
   obj.SetCoordXRelativeInit(obj.CoordXRelative());
   obj.SetCoordYRelativeInit(obj.CoordYRelative());
   obj.SetVisibleFlag(this.IsVisible(),false);
   obj.SetActive(this.Active());
   obj.SetEnabled(this.Enabled());
   return obj;
  }
//+------------------------------------------------------------------+

...

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//+------------------------------------------------------------------+
bool CForm::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                             const int x,
                             const int y,
                             const int w,
                             const int h,
                             const color colour,
                             const uchar opacity,
                             const bool activity,
                             const bool redraw)
  {
//--- Создаём новый графический элемент
   CGCnvElement *obj=this.CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
//--- Если объект создан - рисуем добавленный объект и возвращаем true
   if(obj==NULL)
      return false;
   obj.SetMain(this.GetMain()==NULL ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetBase());
   obj.Erase(colour,opacity,redraw);
   return true;
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт объект тени                                              |
//+------------------------------------------------------------------+
void CForm::CreateShadowObj(const color colour,const uchar opacity)
  {
//--- Если флаг тени выключен, или объект тени уже существует - уходим
   if(!this.m_shadow || this.m_shadow_obj!=NULL)
      return;
//--- Рассчитываем координаты объекта тени в соответствии с отступом сверху и слева
   int x=this.CoordX()-OUTER_AREA_SIZE;
   int y=this.CoordY()-OUTER_AREA_SIZE;
//--- Рассчитываем ширину и высоту в соответствии с отступом сверху, снизу, слева и справа
   int w=this.Width()+OUTER_AREA_SIZE*2;
   int h=this.Height()+OUTER_AREA_SIZE*2;
//--- Создаём новый объект тени и записываем указатель на него в переменную
   this.m_shadow_obj=new CShadowObj(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),this.NameObj()+"Shadow",x,y,w,h);
   if(this.m_shadow_obj==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_FORM_OBJECT_ERR_FAILED_CREATE_SHADOW_OBJ));
      return;
     }
   this.m_list_tmp.Add(this.m_shadow_obj);
//--- Устанавливаем свойства созданному объекту-тени
   this.m_shadow_obj.SetID(this.ID());
   this.m_shadow_obj.SetNumber(-1);
   this.m_shadow_obj.SetOpacity(opacity);
   this.m_shadow_obj.SetColor(colour);
   this.m_shadow_obj.SetMovable(this.Movable());
   this.m_shadow_obj.SetActive(false);
   this.m_shadow_obj.SetVisibleFlag(false,false);
//--- Объект-форму перемещаем на передний план
   this.BringToTop();
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Обновляет координаты элемента                                    |
//+------------------------------------------------------------------+
bool CForm::Move(const int x,const int y,const bool redraw=false)
  {
//--- Получаем указатели на базовый и главный объекты в иерархии связанных объектов и объект-тень
   CGCnvElement *base=this.GetBase();
   CGCnvElement *main=this.GetMain();
   bool res=true;
//--- Если элемент не перемещаемый, и это независимый объект - уходим
   if(!this.IsDependent() && !this.Movable())
      return false;
//--- Если есть тень у объекта и не удалось записать новые значения координат в свойства объекта-тени - возвращаем false
   if(this.m_shadow && this.m_shadow_obj!=NULL)
     {
      if(!this.m_shadow_obj.Move(x-OUTER_AREA_SIZE+this.m_shadow_obj.CoordXRelative(),y-OUTER_AREA_SIZE+this.m_shadow_obj.CoordYRelative(),false))
         return false;
     }
//--- Если не удалось записать новые значения координат в свойства графического объекта - возвращаем false
   if(!this.SetCoordX(x) || !this.SetCoordY(y))
      return false;
//--- Смещаем все привязанные объекты
   if(!this.MoveDependentObj(x,y,false))
      return false;
   //--- Если стоит флаг обновления, и это главный объект - перерисовываем график.
   if(this.IsMain() && redraw)
      ::ChartRedraw(this.ChartID());
   //--- Возвращаем true
   return true;
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт объект-форму на указанном чарте в заданном подокне       |
//+------------------------------------------------------------------+
CForm *CGraphElmControl::CreateForm(const int form_id,const long chart_id,const int wnd,const string name,const int x,const int y,const int w,const int h)
  {
   CForm *form=new CForm(NULL,NULL,chart_id,wnd,name,x,y,w,h);
   if(form==NULL)
      return NULL;
   form.SetID(form_id);
   form.SetNumber(0);
   return form;
  }
//+------------------------------------------------------------------+

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


Точно так же укажем значения NULL в строке инициализации конструктоа класса формы управления опорными точками графического объекта в файле \MQL5\Include\DoEasy\Objects\Graph\Extend\CGStdGraphObjExtToolkit.mqh:

//+------------------------------------------------------------------+
//| Класс формы управления опорными точками графического объекта     |
//+------------------------------------------------------------------+
class CFormControl : public CForm
  {
private:
   bool              m_drawn;                   // Флаг, указывающий, что точка управления нарисована на форме
   int               m_pivot_point;             // Опорная точка, которой управляет форма
public:
//--- (1) Возвращает, (2) устанавливает флаг нарисованной точки
   bool              IsControlAlreadyDrawn(void)               const { return this.m_drawn;                       }
   void              SetControlPointDrawnFlag(const bool flag)       { this.m_drawn=flag;                         }
//--- (1) Возвращает, (2) устанавливает опорную точку, которой управляет форма
   int               GraphObjPivotPoint(void)                  const { return this.m_pivot_point;                 }
   void              SetGraphObjPivotPoint(const int index)          { this.m_pivot_point=index;                  }
//--- Конструктор
                     CFormControl(void)                              { this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL;  }
                     CFormControl(const long chart_id,const int subwindow,const string name,const int pivot_point,const int x,const int y,const int w,const int h) :
                        CForm(GRAPH_ELEMENT_TYPE_FORM,NULL,NULL,chart_id,subwindow,name,x,y,w,h)
                          {
                           this.m_type=OBJECT_DE_TYPE_GFORM_CONTROL;
                           this.m_pivot_point=pivot_point;
                          }
  };
//+------------------------------------------------------------------+


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

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

...

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


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

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CWinFormBase::Redraw(bool redraw)
  {
//--- Если тип объекта меньше, чем "Базовый WinForms-объект", или объект не должен отображаться - уходим
   if(this.TypeGraphElement()<GRAPH_ELEMENT_TYPE_WF_BASE || !this.Displayed())
      return;
//--- Получим объект "Тень"

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

//--- При установленном флаге перерисовки, и если это главный объект, к которому прикреплены остальные -
//--- перерисуем чарт для немедленного отображения изменений
   if(this.IsMain() && redraw)
      ::ChartRedraw(this.ChartID());
  }
//+------------------------------------------------------------------+

Ранее здесь проверка выполнялась таким образом:

if(redraw && this.GetMain()==NULL)

В принципе и по сути это одно и то же, но раз есть метод, то почему его не использовать?

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

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

CommonBase.mqh, Button.mqh, ElementsListBox.mqh, RadioButton.mqh, ArrowButton.mqh, ArrowLeftButton.mqh, ArrowRightButton.mqh, ArrowUpButton.mqh, ArrowDownButton.mqh, ListBoxItem.mqh, TabHeader.mqh.

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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CButtonListBox::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)
  {
//--- создаём объект CButton
   CGCnvElement *element=new CButton(this.GetMain(),this.GetObject(),this.ChartID(),this.SubWindow(),descript,x,y,w,h);
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
//--- установим объекту флаг перемещаемости и относительные координаты
   element.SetMovable(movable);
   element.SetCoordXRelative(element.CoordX()-this.CoordX());
   element.SetCoordYRelative(element.CoordY()-this.CoordY());
   return element;
  }
//+------------------------------------------------------------------+


Такие же или аналогичные доработки методов, создающий новый графический объект (помимо доработки конструкторов), сделаны в файлах:

ButtonListBox.mqh, CheckedListBox.mqh, ListBox.mqh, ArrowLeftRightBox.mqh, ArrowUpDownBox.mqh.

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


Вспомогательный объект "Подсказка" и его производные

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


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

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


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

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

protected:
   //--- Рисует подсказку
   virtual void      DrawHint(const int shift) {return;}
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CHintBase(const ENUM_GRAPH_ELEMENT_TYPE type,
                               CGCnvElement *main_obj,CGCnvElement *base_obj,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
public:
//--- (1) Устанавливает, (2) возвращает цвет подсказки
   void              SetHintColor(const color clr)       { this.SetForeColor(clr,false);  }
   color             HintColor(void)               const { return this.ForeColor();       }
//--- Конструктор
                     CHintBase(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);
  };
//+------------------------------------------------------------------+

Методы, устанавливающий и возвращающий цвет подсказки на самом деле устанавливают и возвращают цвет текста объекта — ForeColor().

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

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

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

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

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

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

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


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

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CHintBase::Redraw(bool redraw)
  {
//--- Заполняем объект цветом фона с прозрачностью
   this.Erase(this.BackgroundColor(),this.Opacity(),true);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Очищает элемент с заполнением его цветом и непрозрачностью       |
//+------------------------------------------------------------------+
void CHintBase::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(1);
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Логика метода полностью расписана в комментариях к коду.


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

//+------------------------------------------------------------------+
//| Очищает элемент заливкой градиентом                              |
//+------------------------------------------------------------------+
void CHintBase::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(1);
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Этот метод точно так же, как и предыдущий заливает фон цветом, но не одним, а из массива цветов, рисует рамку при её наличии и подсказку.


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

//+------------------------------------------------------------------+
//| Рисует рамку элемента                                            |
//+------------------------------------------------------------------+
void CHintBase::DrawFrame(void)
  {
   this.DrawRectangle(0,0,this.Width()-1,this.Height()-1,this.BorderColor(),this.Opacity());
  }
//+------------------------------------------------------------------+

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


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

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

Логика метода полностью расписана в комментариях к коду.


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

Создадим объект-подсказку о возможности смещения влево.

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

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


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

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

protected:
   //--- Рисует подсказку
   virtual void      DrawHint(const int shift);
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CHintMoveLeft(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:
//--- Обработчик события  Курсор в пределах активной области, кнопки мышки не нажаты
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Конструктор
                     CHintMoveLeft(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);
  };
//+------------------------------------------------------------------+


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

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

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

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


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

//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CHintMoveLeft::CHintMoveLeft(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_HINT_MOVE_LEFT,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Рисует подсказку                                                 |
//+------------------------------------------------------------------+
void CHintMoveLeft::DrawHint(const int shift)
  {
   int w=this.Width();
   int h=this.Height();
   int middle=int(h*0.5);
   this.DrawRectangleFill(w-2,0,w-1,h-1,this.HintColor(),255);
   this.DrawTriangleFill(shift,middle,shift+3,middle-3,shift+3,middle+3,this.HintColor(),255);
   this.DrawLine(shift+3,middle,w-3,middle,this.HintColor(),255);
  }
//+------------------------------------------------------------------+

Здесь: получаем ширину и высоту всего объекта, рассчитываем центральную линию, от которой будут отсчитываться значения для построения стрелки. Рисуем залитый цветом вертикальный прямоугольник шириной в два пикселя с правой строны объекта. Рисуем треугольник, углы которого отсчитаны от значения shift, переданного в метод, и центральной линии. В конце рисуем горизонтальную центральную линию, начиная от левой грани с отступом на значение shift, передаваемое в метод, и до нарисованного вертикального прямоугольника с правой стороны объекта. Таким образом получаем стрелку влево с основанием с правой стороны объекта. При этом длина стрелки зависит от значения shift, передаваемого в метод. Чем больше значение этой переменной, тем короче получается стрелка.

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

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

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CHintMoveLeft::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Устанавливаем флаг неотображения объекта и скрываем его
   this.SetDisplayed(false);
   this.Hide();
//--- Получаем указатель на базовый объект
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Из базового объекта получаем указатели на объекты-подсказки "смещение вправо, вверх и вниз",
//--- устанавливаем для них флаг неотображения объекта и скрываем элементы управления
   CWinFormBase *hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0);
   if(hint_mr!=NULL)
     {
      hint_mr.SetDisplayed(false);
      hint_mr.Hide();
     }
   CWinFormBase *hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0);
   if(hint_mu!=NULL)
     {
      hint_mu.SetDisplayed(false);
      hint_mu.Hide();
     }
   CWinFormBase *hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0);
   if(hint_md!=NULL)
     {
      hint_md.SetDisplayed(false);
      hint_md.Hide();
     }
  }
//+------------------------------------------------------------------+

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


Создадим объект-подсказку о возможности смещения вправо.

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

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

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

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

protected:
   //--- Рисует подсказку
   virtual void      DrawHint(const int shift);
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CHintMoveRight(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:
//--- Обработчик события  Курсор в пределах активной области, кнопки мышки не нажаты
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Конструктор
                     CHintMoveRight(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);
  };
//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CHintMoveRight::CHintMoveRight(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);
  }
//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CHintMoveRight::CHintMoveRight(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_HINT_MOVE_RIGHT,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Рисует подсказку                                                 |
//+------------------------------------------------------------------+
void CHintMoveRight::DrawHint(const int shift)
  {
   int w=this.Width();
   int h=this.Height();
   int middle=int(h*0.5);
   this.DrawRectangleFill(0,0,1,h-1,this.HintColor(),255);
   this.DrawTriangleFill(shift+8,middle,shift+8-3,middle+3,shift+8-3,middle-3,this.HintColor(),255);
   this.DrawLine(2,middle,shift+4,middle,this.HintColor(),255);
  }
//+------------------------------------------------------------------+

Здесь мы рисуем вертикальный прямоугольник с левой стороны объекта, треугольник стрелки рисуем от значения shift плюс 8 (длина линии), а горизонтальную центральную линию рисуем, начиная от нарисованного прямоугольника-основания и заканчивая нарисованным треугольником. При увеличении значения shift длина стрелки увеличивается.

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

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CHintMoveRight::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Устанавливаем флаг неотображения объекта и скрываем его
   this.SetDisplayed(false);
   this.Hide();
//--- Получаем указатель на базовый объект
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Из базового объекта получаем указатели на объекты-подсказки "смещение влево, вверх и вниз",
//--- устанавливаем для них флаг неотображения объекта и скрываем элементы управления
   CWinFormBase *hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0);
   if(hint_ml!=NULL)
     {
      hint_ml.SetDisplayed(false);
      hint_ml.Hide();
     }
   CWinFormBase *hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0);
   if(hint_mu!=NULL)
     {
      hint_mu.SetDisplayed(false);
      hint_mu.Hide();
     }
   CWinFormBase *hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0);
   if(hint_md!=NULL)
     {
      hint_md.SetDisplayed(false);
      hint_md.Hide();
     }
  }
//+------------------------------------------------------------------+

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


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

Класс объекта-помощника, указывающий на возможность смещения вверх:

//+------------------------------------------------------------------+
//|                                                   HintMoveUp.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 "HintBase.mqh"
//+------------------------------------------------------------------+
//| Класс объекта HintMoveUp элементов управления WForms             |
//+------------------------------------------------------------------+
class CHintMoveUp : public CHintBase
  {

protected:
   //--- Рисует подсказку
   virtual void      DrawHint(const int shift);
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CHintMoveUp(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:
//--- Обработчик события  Курсор в пределах активной области, кнопки мышки не нажаты
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Конструктор
                     CHintMoveUp(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);
  };
//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CHintMoveUp::CHintMoveUp(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);
  }
//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CHintMoveUp::CHintMoveUp(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_HINT_MOVE_UP,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP);
  }
//+------------------------------------------------------------------+
//| Рисует подсказку                                                 |
//+------------------------------------------------------------------+
void CHintMoveUp::DrawHint(const int shift)
  {
   int w=this.Width();
   int h=this.Height();
   int middle=int(w*0.5);
   this.DrawRectangleFill(0,h-2,w-1,h-1,this.HintColor(),255);
   this.DrawTriangleFill(middle,shift,middle+3,shift+3,middle-3,shift+3,this.HintColor(),255);
   this.DrawLine(middle,shift+4,middle,h-3,this.HintColor(),255);
  }
//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CHintMoveUp::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Устанавливаем флаг неотображения объекта и скрываем его
   this.SetDisplayed(false);
   this.Hide();
//--- Получаем указатель на базовый объект
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Из базового объекта получаем указатели на объекты-подсказки "смещение вниз, влево и вправо",
//--- устанавливаем для них флаг неотображения объекта и скрываем элементы управления
   CWinFormBase *hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0);
   if(hint_md!=NULL)
     {
      hint_md.SetDisplayed(false);
      hint_md.Hide();
     }
   CWinFormBase *hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0);
   if(hint_ml!=NULL)
     {
      hint_ml.SetDisplayed(false);
      hint_ml.Hide();
     }
   CWinFormBase *hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0);
   if(hint_mr!=NULL)
     {
      hint_mr.SetDisplayed(false);
      hint_mr.Hide();
     }
  }
//+------------------------------------------------------------------+


Класс объекта-помощника, указывающий на возможность смещения вниз:

//+------------------------------------------------------------------+
//|                                                 HintMoveDown.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 "HintBase.mqh"
//+------------------------------------------------------------------+
//| Класс объекта HintMoveDown элементов управления WForms           |
//+------------------------------------------------------------------+
class CHintMoveDown : public CHintBase
  {

protected:
   //--- Рисует подсказку
   virtual void      DrawHint(const int shift);
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CHintMoveDown(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:
//--- Обработчик события  Курсор в пределах активной области, кнопки мышки не нажаты
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Конструктор
                     CHintMoveDown(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);
  };
//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CHintMoveDown::CHintMoveDown(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);
  }
//+------------------------------------------------------------------+
//| Конструктор с указанием главного и базового объектов,            |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CHintMoveDown::CHintMoveDown(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_HINT_MOVE_DOWN,main_obj,base_obj,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN);
  }
//+------------------------------------------------------------------+
//| Рисует подсказку                                                 |
//+------------------------------------------------------------------+
void CHintMoveDown::DrawHint(const int shift)
  {
   int w=this.Width();
   int h=this.Height();
   int middle=int(w*0.5);
   this.DrawRectangleFill(0,0,w-1,1,this.HintColor(),255);
   this.DrawTriangleFill(middle,shift+8,middle-3,shift+8-3,middle+3,shift+8-3,this.HintColor(),255);
   this.DrawLine(middle,2,middle,shift+8-4,this.HintColor(),255);
  }
//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CHintMoveDown::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Устанавливаем флаг неотображения объекта и скрываем его
   this.SetDisplayed(false);
   this.Hide();
//--- Получаем указатель на базовый объект
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Из базового объекта получаем указатели на объекты-подсказки "смещение вверх, влево и вправо",
//--- устанавливаем для них флаг неотображения объекта и скрываем элементы управления
   CWinFormBase *hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0);
   if(hint_mu!=NULL)
     {
      hint_mu.SetDisplayed(false);
      hint_mu.Hide();
     }
   CWinFormBase *hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0);
   if(hint_ml!=NULL)
     {
      hint_ml.SetDisplayed(false);
      hint_ml.Hide();
     }
   CWinFormBase *hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0);
   if(hint_mr!=NULL)
     {
      hint_mr.SetDisplayed(false);
      hint_mr.Hide();
     }
  }
//+------------------------------------------------------------------+

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


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

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

//+------------------------------------------------------------------+
//| Устанавливает параметры присоединённому объекту                  |
//+------------------------------------------------------------------+
void CContainer::SetObjParams(CWinFormBase *obj,const color colour)
  {
   obj.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
   obj.SetBase(this.GetObject());
//--- Устанавливаем объекту цвет текста как у базового контейнера
   obj.SetForeColor(this.ForeColor(),true);

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

//---...
//---...
      //--- Для WinForms-объекта"ArrowButton"
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
        obj.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
        obj.SetBorderStyle(FRAME_STYLE_SIMPLE);
        break;
      //--- Для WinForms-объекта "Hint"
      case GRAPH_ELEMENT_TYPE_WF_HINT_BASE            :
        obj.SetBackgroundColor(CLR_CANV_NULL,true);
        obj.SetBorderColor(CLR_CANV_NULL,true);
        obj.SetForeColor(CLR_CANV_NULL,true);
        obj.SetOpacity(0,false);
        obj.SetBorderStyle(FRAME_STYLE_NONE);
        break;
      //--- Для WinForms-объекта "HintMoveLeft", "HintMoveRight", "HintMoveUp", "HintMoveDown", 
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT       :
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT      :
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP         :
      case GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN       :
        obj.SetBackgroundColor(CLR_CANV_NULL,true);
        obj.SetBorderColor(CLR_CANV_NULL,true);
        obj.SetForeColor(CLR_DEF_CONTROL_HINT_FORE_COLOR,true);
        obj.SetOpacity(0,false);
        obj.SetBorderStyle(FRAME_STYLE_NONE);
        break;
      default:
        break;
     }
   obj.Crop();
  }
//+------------------------------------------------------------------+


В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh класса объекта-панели в список включаемых файлов добавим файлы новых созданных классов:

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

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

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

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

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

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

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

//+------------------------------------------------------------------+
//| Создаёт объект-подложку                                          |
//+------------------------------------------------------------------+
bool CPanel::CreateUnderlayObj(void)
  {
   this.m_underlay=new CGCnvElement(GRAPH_ELEMENT_TYPE_WF_UNDERLAY,this.GetMain(),this.GetObject(),this.ID(),this.Number(),
                                    this.ChartID(),this.SubWindow(),this.NameObj()+"Underlay",
                                    this.CoordXWorkspace(),this.CoordYWorkspace(),this.WidthWorkspace(),this.HeightWorkspace(),
                                    CLR_CANV_NULL,0,false,false);
   if(m_underlay==NULL)
     {
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_FAILED_CREATE_UNDERLAY_OBJ);
      return false;
     }
   if(!this.m_list_tmp.Add(this.m_underlay))
     {
      CMessage::ToLog(DFUN,MSG_LIB_SYS_FAILED_OBJ_ADD_TO_LIST);
      delete this.m_underlay;
      return false;
     }
   this.SetUnderlayParams();
   return true;
  }
//+------------------------------------------------------------------+


В файле \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_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;
      default  : break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

Идентичные доработки этого метода сделаны во всех остальных классах объектов-контейнеров в файлах TabControl.mqh, TabField.mqh, SplitContainerPanel.mqh, и далее доработки этих методов здесь рассматривать не будем.


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

      header.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      header.SetBase(this.GetObject());

для объектов-полей вкладок

      field.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      field.SetBase(this.GetObject());

для объектов-кнопок вправо-влево и вверх-вниз

//--- Создаём объект кнопки влево-вправо
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,this.Width()-32,0,15,15,clrNONE,255,this.Active(),false);
//--- Получаем указатель на вновь созданный объект
   CArrowLeftRightBox *box_lr=this.GetArrLeftRightBox();
   if(box_lr!=NULL)
     {
      this.SetVisibleLeftRightBox(false);
      this.SetSizeLeftRightBox(box_lr.Width());
      box_lr.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      box_lr.SetBase(this.GetObject());
      box_lr.SetID(this.GetMaxIDAll());
      box_lr.SetBorderStyle(FRAME_STYLE_NONE);
      box_lr.SetBackgroundColor(CLR_CANV_NULL,true);
      box_lr.SetOpacity(0);
      box_lr.Hide();
      CArrowLeftButton *lb=box_lr.GetArrowLeftButton();
      if(lb!=NULL)
        {
         lb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         lb.SetBase(box_lr);
         lb.SetID(this.GetMaxIDAll());
        }
      CArrowRightButton *rb=box_lr.GetArrowRightButton();
      if(rb!=NULL)
        {
         rb.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         rb.SetBase(box_lr);
         rb.SetID(this.GetMaxIDAll());
        }
     }
//--- Создаём объект кнопки вверх-вниз
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,0,this.Height()-32,15,15,clrNONE,255,this.Active(),false);
//--- Получаем указатель на вновь созданный объект
   CArrowUpDownBox *box_ud=this.GetArrUpDownBox();
   if(box_ud!=NULL)
     {
      this.SetVisibleUpDownBox(false);
      this.SetSizeUpDownBox(box_ud.Height());
      box_ud.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
      box_ud.SetBase(this.GetObject());
      box_ud.SetID(this.GetMaxIDAll());
      box_ud.SetBorderStyle(FRAME_STYLE_NONE);
      box_ud.SetBackgroundColor(CLR_CANV_NULL,true);
      box_ud.SetOpacity(0);
      box_ud.Hide();
      CArrowDownButton *db=box_ud.GetArrowDownButton();
      if(db!=NULL)
        {
         db.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         db.SetBase(box_ud);
         db.SetID(this.GetMaxIDAll());
        }
      CArrowUpButton *ub=box_ud.GetArrowUpButton();
      if(ub!=NULL)
        {
         ub.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         ub.SetBase(box_ud);
         ub.SetID(this.GetMaxIDAll());
        }
     }
//--- Выстраиваем все заголовки в соответствии с установленными режимами их отображения и выбираем указанную вкладку
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+


Доработаем класс элемента управления SplitContainer в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\SplitContainer.mqh.

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

//--- Возвращает указатель на разделитель
   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);                }

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


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

         return;
      for(int i=0;i<2;i++)
        {
         CSplitContainerPanel *panel=this.GetPanel(i);
         if(panel==NULL)
            continue;
         panel.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         panel.SetBase(this.GetObject());
        }
      //---
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false))
         return;
      CSplitter *splitter=this.GetSplitter();
      if(splitter!=NULL)
        {
         splitter.SetMain(this.IsMain() ? this.GetObject() : this.GetMain());
         splitter.SetBase(this.GetObject());
         splitter.SetMovable(true);
         splitter.SetDisplayed(false);
         splitter.Hide();
        }


Кроме того, добавим в метод создание четырёх объектов-подсказок и установку их параметров:

//+------------------------------------------------------------------+
//| Создаёт панели                                                   |
//+------------------------------------------------------------------+
void CSplitContainer::CreatePanels(void)
  {
   this.m_list_elements.Clear();
   if(this.SetsPanelParams())
     {
      //--- Создаём две панели
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel1_x,this.m_panel1_y,this.m_panel1_w,this.m_panel1_h,clrNONE,255,true,false))
         return;
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLIT_CONTAINER_PANEL,this.m_panel2_x,this.m_panel2_y,this.m_panel2_w,this.m_panel2_h,clrNONE,255,true,false))
         return;
      //--- Создаём объект-разделитель
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_SPLITTER,this.m_splitter_x,this.m_splitter_y,this.m_splitter_w,this.m_splitter_h,clrNONE,255,true,false))
         return;
      CSplitter *splitter=this.GetSplitter();
      if(splitter!=NULL)
        {
         splitter.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,this.SplitterOrientation());
         splitter.SetMovable(true);
         splitter.SetDisplayed(false);
         splitter.Hide();
        }
      //--- Создаём объект HintMoveLeft
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,this.m_splitter_x-DEF_HINT_ICON_SIZE,this.m_splitter_y,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false))
         return;
      CHintMoveLeft *hint_ml=this.GetHintMoveLeft();
      if(hint_ml!=NULL)
        {
         hint_ml.SetMovable(false);
         hint_ml.SetDisplayed(false);
         hint_ml.Hide();
        }
      //--- Создаём объект HintMoveRight
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,this.m_splitter_x+this.m_splitter_w,this.m_splitter_y,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false))
         return;
      CHintMoveRight *hint_mr=this.GetHintMoveRight();
      if(hint_mr!=NULL)
        {
         hint_mr.SetMovable(false);
         hint_mr.SetDisplayed(false);
         hint_mr.Hide();
        }
      //--- Создаём объект HintMoveUp
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,this.m_splitter_x,this.m_splitter_y-DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false))
         return;
      CHintMoveUp *hint_mu=this.GetHintMoveUp();
      if(hint_mu!=NULL)
        {
         hint_mu.SetMovable(false);
         hint_mu.SetDisplayed(false);
         hint_mu.Hide();
        }
      //--- Создаём объект HintMoveDown
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,this.m_splitter_x,this.m_splitter_y+this.m_splitter_h,DEF_HINT_ICON_SIZE,DEF_HINT_ICON_SIZE,clrNONE,255,true,false))
         return;
      CHintMoveDown *hint_md=this.GetHintMoveDown();
      if(hint_md!=NULL)
        {
         hint_md.SetMovable(false);
         hint_md.SetDisplayed(false);
         hint_md.Hide();
        }
     }
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| устанавливает расположение разделителя                           |
//+------------------------------------------------------------------+
void CSplitContainer::SetSplitterOrientation(const ENUM_CANV_ELEMENT_SPLITTER_ORIENTATION value,const bool only_prop)
  {
   this.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value);
//--- Если панелей или разделителя нет - уходим
   CSplitContainerPanel *p1=this.GetPanel1();
   CSplitContainerPanel *p2=this.GetPanel2();
   CSplitter *sp=this.GetSplitter();
   if(p1==NULL || p2==NULL || sp==NULL)
      return;
//--- Устанавливаем для объекта-разделителя его свойство ориентации
   sp.SetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION,value);
//--- Если только установка свойства - уходим
   if(only_prop)
      return;
//--- Устанавливаем параметры панелей и разделителя
   this.SetsPanelParams();
//--- Если размеры панели 1 успешно изменены
   if(p1.Resize(this.m_panel1_w,this.m_panel1_h,true))
     {
      //--- Если координаты панели 2 изменены на новые
      if(p2.Move(this.CoordX()+this.m_panel2_x,this.CoordY()+this.m_panel2_y,true))
        {
         //--- если размеры панели 2 успешно изменены
         if(p2.Resize(this.m_panel2_w,this.m_panel2_h,true))
           {
            //--- устанавливаем новые относительные координаты панели 2
            p2.SetCoordXRelative(p2.CoordX()-this.CoordX());
            p2.SetCoordYRelative(p2.CoordY()-this.CoordY());
           }
        }
      //--- Если размеры объекта-разделителя успешно изменены - 
      //--- устанавливаем новые значения координат разделителя
      if(sp.Resize(this.m_splitter_w,this.m_splitter_h,false))
         this.SetSplitterDistance(this.SplitterDistance(),true);
     }
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Обработчик событий                                               |
//+------------------------------------------------------------------+
void CSplitContainer::OnChartEvent(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Корректируем смещение по Y для подокна
   CGCnvElement::OnChartEvent(id,lparam,dparam,sparam);
//--- Если идентификатор события - перемещение разделителя
   if(id==WF_CONTROL_EVENT_MOVING)
     {
      //--- Получаем указатель на объект-разделитель
      CSplitter *splitter=this.GetSplitter();
      if(splitter==NULL || this.SplitterFixed())
         return;
      //--- Получаем указатели на объекты-подсказки
      CHintMoveLeft *hint_ml=this.GetHintMoveLeft();
      CHintMoveRight *hint_mr=this.GetHintMoveRight();
      CHintMoveUp *hint_mu=this.GetHintMoveUp();
      CHintMoveDown *hint_md=this.GetHintMoveDown();
      if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL)
         return;
      
      //--- Отключаем отображение подсказок и скрываем их
      hint_ml.SetDisplayed(false);
      hint_ml.Hide();
      hint_mr.SetDisplayed(false);
      hint_mr.Hide();
      hint_mu.SetDisplayed(false);
      hint_mu.Hide();
      hint_md.SetDisplayed(false);
      hint_md.Hide();
         
      //--- Объявляем переменные для координат разделителя
      int x=(int)lparam;
      int y=(int)dparam;
      //--- В зависимости от ориентации разделителя
      switch(this.SplitterOrientation())
        {
         //--- вертикальное положение разделителя
         case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
           //--- Устанавливаем координату Y равной координате Y элемента управления
           y=this.CoordY();
           //--- Корректируем координату X так, чтобы разделитель не выходил за пределы элемента управления
           //--- с учётом получающейся в итоге минимальной ширины панелей
           if(x<this.CoordX()+this.Panel1MinSize())
              x=this.CoordX()+this.Panel1MinSize();
           if(x>this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth())
              x=this.CoordX()+this.Width()-this.Panel2MinSize()-this.SplitterWidth();
           break;
         //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
         //--- горизонтальное положение разделителя
         default:
           //--- Устанавливаем координату X равной координате X элемента управления
           x=this.CoordX();
           //--- Корректируем координату Y так, чтобы разделитель не выходил за пределы элемента управления
           //--- с учётом получающейся в итоге минимальной высоты панелей
           if(y<this.CoordY()+this.Panel1MinSize())
              y=this.CoordY()+this.Panel1MinSize();
           if(y>this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth())
              y=this.CoordY()+this.Height()-this.Panel2MinSize()-this.SplitterWidth();
           break;
        }
      //--- Рисуем пустой прямоугольник
      this.DrawRectangleEmpty();
      //--- Если разделитель смещён на рассчитанные координаты
      if(splitter.Move(x,y,true))
        {
         //--- устанавливаем разделителю его относительные координаты
         splitter.SetCoordXRelative(splitter.CoordX()-this.CoordX());
         splitter.SetCoordYRelative(splitter.CoordY()-this.CoordY());
         //--- В зависимости от ориентации разделителя устанавливаем его новые координаты
         this.SetSplitterDistance(!this.SplitterOrientation() ? splitter.CoordX()-this.CoordX() : splitter.CoordY()-this.CoordY(),false);
        }
     }
  }
//+------------------------------------------------------------------+

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

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

В обработчике события "Курсор в пределах активной области, кнопки мышки не нажаты" добавим стирание пунктирного прямоугольника и скрытие объектов-подсказок:

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

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


Как только курсор заходит в область управления, где находится разделитель, необходимо отобразить подсказки о возможном смещении разделителя влево-вправо или вверх-вниз в зависимости от его ориентации.

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

//+------------------------------------------------------------------+
//| Обработчик события  Курсор в пределах области управления,        |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CSplitContainer::MouseControlAreaNotPressedHandler(const int id,const long &lparam,const double &dparam,const string &sparam)
  {
//--- Если разделитель неперемещаемый - уходим
   if(this.SplitterFixed())
      return;
//--- Рисуем пустой прямоугольник в области управления
   this.DrawRectangleEmpty();
//--- Рисуем пунктирный прямоугольник в области управления
   this.DrawRectangleDotted();
//--- Получаем указатель на разделитель
   CSplitter *splitter=this.GetSplitter();
   if(splitter==NULL)
     {
      ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
      return;
     }
//--- Если разделитель не отображается
   if(!splitter.Displayed())
     {
      //--- Включаем отображение разделителя и показываем его
      splitter.SetDisplayed(true);
      splitter.Erase(true);
      splitter.Show();
     }
//--- Получаем указатели на объекты-подсказки
   CHintMoveLeft *hint_ml=this.GetHintMoveLeft();
   CHintMoveRight *hint_mr=this.GetHintMoveRight();
   CHintMoveUp *hint_mu=this.GetHintMoveUp();
   CHintMoveDown *hint_md=this.GetHintMoveDown();
   if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL)
      return;
//--- Получаем координаты курсора
   int x=this.m_mouse.CoordX()-this.CoordX();
   int y=this.m_mouse.CoordY()-this.CoordY();
//--- В зависимости от ориентации разделителя
   switch(this.SplitterOrientation())
     {
      //--- вертикальное положение разделителя
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
        //--- Устанавливаем и корректируем координаты объекта-подсказки "смещение влево"
        x=this.CoordX()+this.m_splitter_x-hint_ml.Width();//-1;
        y+=this.CoordY()-hint_ml.Height()-1;
        if(y<this.CoordY()+this.m_splitter_y)
           y=this.CoordY()+this.m_splitter_y;
        //--- Смещаем объект-подсказку на рассчитанные координаты, устанавливаем ему флаг видимости и показываем объект
        if(hint_ml.Move(x,y))
          {
           hint_ml.SetCoordXRelative(x-this.CoordX());
           hint_ml.SetCoordYRelative(y-this.CoordY());
           hint_ml.SetDisplayed(true);
           hint_ml.Show();
          }
        //--- Устанавливаем и корректируем координаты объекта-подсказки "смещение вправо"
        x=this.CoordX()+this.m_splitter_x+this.m_splitter_w;//+1;
        //--- Смещаем объект-подсказку на рассчитанные координаты, устанавливаем ему флаг видимости и показываем объект
        if(hint_mr.Move(x,y))
          {
           hint_mr.SetCoordXRelative(x-this.CoordX());
           hint_mr.SetCoordYRelative(y-this.CoordY());
           hint_mr.SetDisplayed(true);
           hint_mr.Show();
          }
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      //--- горизонтальное положение разделителя
      default:
        //--- Устанавливаем и корректируем координаты объекта-подсказки "смещение вверх"
        y=this.CoordY()+this.m_splitter_y-hint_mu.Height();//-1;
        x+=this.CoordX()-hint_mu.Width()-1;
        if(x<this.CoordX()+this.m_splitter_x)
           x=this.CoordX()+this.m_splitter_x;
        //--- Смещаем объект-подсказку на рассчитанные координаты, устанавливаем ему флаг видимости и показываем объект
        if(hint_mu.Move(x,y))
          {
           hint_mu.SetCoordXRelative(x-this.CoordX());
           hint_mu.SetCoordYRelative(y-this.CoordY());
           hint_mu.SetDisplayed(true);
           hint_mu.Show();
          }
        //--- Устанавливаем и корректируем координаты объекта-подсказки "смещение вниз"
        y=this.CoordY()+this.m_splitter_y+this.m_splitter_h;//+1;
        //--- Смещаем объект-подсказку на рассчитанные координаты, устанавливаем ему флаг видимости и показываем объект
        if(hint_md.Move(x,y))
          {
           hint_md.SetCoordXRelative(x-this.CoordX());
           hint_md.SetCoordYRelative(y-this.CoordY());
           hint_md.SetDisplayed(true);
           hint_md.Show();
          }
        break;
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Обработчик последнего события мышки                              |
//+------------------------------------------------------------------+
void CSplitContainer::OnMouseEventPostProcessing(void)
  {
   if(!this.IsVisible() || !this.Enabled() || !this.Displayed())
      return;
   ENUM_MOUSE_FORM_STATE state=this.GetMouseState();
   switch(state)
     {
      //--- Курсор за пределами формы, кнопки мышки не нажаты
      //--- Курсор за пределами формы, нажата кнопка мышки (любая)
      //--- Курсор за пределами формы, прокручивается колёсико мышки
      case MOUSE_FORM_STATE_OUTSIDE_FORM_NOT_PRESSED        :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_PRESSED            :
      case MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL              :
      case MOUSE_FORM_STATE_NONE                            :
        if(this.MouseEventLast()==MOUSE_EVENT_INSIDE_ACTIVE_AREA_NOT_PRESSED  ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_FORM_NOT_PRESSED         ||
           this.MouseEventLast()==MOUSE_EVENT_OUTSIDE_FORM_NOT_PRESSED        ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_NOT_PRESSED ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_PRESSED     ||
           this.MouseEventLast()==MOUSE_EVENT_INSIDE_CONTROL_AREA_WHEEL       ||
           this.MouseEventLast()==MOUSE_EVENT_NO_EVENT)
          {
            //--- Рисуем пустой прямоугольник на месте области управления
            this.DrawRectangleEmpty();
            //--- Получаем указатель на разделитель
            CSplitter *splitter=this.GetSplitter();
            if(splitter==NULL)
              {
               ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),": ",this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_SPLITTER));
               return;
              }
            splitter.SetDisplayed(false);
            splitter.Hide();
            //--- Получаем указатели на объекты-подсказки
            CHintMoveLeft *hint_ml=this.GetHintMoveLeft();
            CHintMoveRight *hint_mr=this.GetHintMoveRight();
            CHintMoveUp *hint_mu=this.GetHintMoveUp();
            CHintMoveDown *hint_md=this.GetHintMoveDown();
            if(hint_ml==NULL || hint_mr==NULL || hint_mu==NULL || hint_md==NULL)
               return;
            //--- Если объект-подсказка отображается - выключаем его отображение и скрываем
            if(hint_ml.Displayed())
              {
               hint_ml.SetDisplayed(false);
               hint_ml.Hide();
              }
            if(hint_mr.Displayed())
              {
               hint_mr.SetDisplayed(false);
               hint_mr.Hide();
              }
            if(hint_mu.Displayed())
              {
               hint_mu.SetDisplayed(false);
               hint_mu.Hide();
              }
            if(hint_md.Displayed())
              {
               hint_md.SetDisplayed(false);
               hint_md.Hide();
              }
            //--- Устанавливаем текущее состояние мышки как последнее
            this.m_mouse_event_last=ENUM_MOUSE_EVENT(state+MOUSE_EVENT_NO_EVENT);
          }
        break;
      //--- Курсор в пределах формы, кнопки мышки не нажаты
      //--- Курсор в пределах формы, нажата кнопка мышки (любая)
      //--- Курсор в пределах формы, прокручивается колёсико мышки
      //--- Курсор в пределах активной области, кнопки мышки не нажаты
      //--- Курсор в пределах активной области, нажата кнопка мышки (любая)
      //--- Курсор в пределах активной области, прокручивается колёсико мышки
      //--- Курсор в пределах активной области, отжата кнопка мышки (левая)
      //--- Курсор в пределах области прокрутки окна, кнопки мышки не нажаты
      //--- Курсор в пределах области прокрутки окна, нажата кнопка мышки (любая)
      //--- Курсор в пределах области прокрутки окна, прокручивается колёсико мышки
      //--- Курсор в пределах области изменения размеров окна, кнопки мышки не нажаты
      //--- Курсор в пределах области изменения размеров окна, нажата кнопка мышки (любая)
      //--- Курсор в пределах области изменения размеров окна, прокручивается колёсико мышки
      //--- Курсор в пределах области резделителя окна, кнопки мышки не нажаты
      //--- Курсор в пределах области резделителя окна, нажата кнопка мышки (любая)
      //--- Курсор в пределах области резделителя окна, прокручивается колёсико мышки
      case MOUSE_FORM_STATE_INSIDE_FORM_NOT_PRESSED         :
      case MOUSE_FORM_STATE_INSIDE_FORM_PRESSED             :
      case MOUSE_FORM_STATE_INSIDE_FORM_WHEEL               :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_ACTIVE_AREA_RELEASED     :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_SCROLL_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_NOT_PRESSED  :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_PRESSED      :
      case MOUSE_FORM_STATE_INSIDE_RESIZE_AREA_WHEEL        :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_NOT_PRESSED:
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_PRESSED    :
      case MOUSE_FORM_STATE_INSIDE_CONTROL_AREA_WHEEL      :
        break;
      //---MOUSE_EVENT_NO_EVENT
      default:
        break;
     }
  }
//+------------------------------------------------------------------+


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

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

Логика метода идентична логике в вышерассмотренных обработчиках.


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

В файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Helpers\Splitter.mqh объявим виртуальный обработчик события:

//--- Полностью очищает элемент
   virtual void      Erase(const bool redraw=false) { CWinFormBase::Erase(redraw);  }
//--- Обработчик события  Курсор в пределах активной области, кнопки мышки не нажаты
   virtual void      MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Обработчик события  Курсор в пределах активной области, нажата кнопка мышки (любая)
   virtual void      MouseActiveAreaPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
//--- Обработчик события  Курсор в пределах активной области, отжата кнопка мышки (левая)
   virtual void      MouseActiveAreaReleasedHandler(const int id,const long& lparam,const double& dparam,const string& sparam);
  };
//+------------------------------------------------------------------+


За пределами тела класса напишем его реализацию:

//+------------------------------------------------------------------+
//| Обработчик события Курсор в пределах активной области,           |
//| кнопки мышки не нажаты                                           |
//+------------------------------------------------------------------+
void CSplitter::MouseActiveAreaNotPressedHandler(const int id,const long& lparam,const double& dparam,const string& sparam)
  {
//--- Получаем указатель на базовый объект
   CWinFormBase *base=this.GetBase();
   if(base==NULL)
      return;
//--- Объявляем указатели на объекты-подсказки
   CWinFormBase *hint_ml=NULL;
   CWinFormBase *hint_mr=NULL;
   CWinFormBase *hint_mu=NULL;
   CWinFormBase *hint_md=NULL;
//--- Получаем координаты курсора
   int x=this.m_mouse.CoordX();
   int y=this.m_mouse.CoordY();
//--- В зависимости от ориентации разделителя
   switch((int)this.GetProperty(CANV_ELEMENT_PROP_SPLIT_CONTAINER_SPLITTER_ORIENTATION))
     {
      //--- вертикальное положение разделителя
      case CANV_ELEMENT_SPLITTER_ORIENTATION_VERTICAL :
         //--- Из базового объекта получаем указатель на объект-подсказку "смещение влево" и на объект-подсказку "смещение вправо"
         hint_ml=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_LEFT,0);
         hint_mr=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_RIGHT,0);
         if(hint_ml==NULL || hint_mr==NULL)
            return;
         //--- Смещаем подсказки вслед за курсором
         hint_ml.Move(hint_ml.CoordX(),(y-hint_ml.Height()<this.CoordY() ? this.CoordY() : y-hint_ml.Height()));
         hint_mr.Move(hint_mr.CoordX(),(y-hint_mr.Height()<this.CoordY() ? this.CoordY() : y-hint_mr.Height()),true);
        break;
      //---CANV_ELEMENT_SPLITTER_ORIENTATION_HORISONTAL
      //--- горизонтальное положение разделителя
      default:
         //--- Из базового объекта получаем указатель на объект-подсказку "смещение вверх" и на объект-подсказку "смещение вниз"
         hint_mu=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_UP,0);
         hint_md=base.GetElementByType(GRAPH_ELEMENT_TYPE_WF_HINT_MOVE_DOWN,0);
         if(hint_mu==NULL || hint_md==NULL)
            return;
         //--- Смещаем подсказки вслед за курсором
         hint_mu.Move((x-hint_mu.Width()<this.CoordX() ? this.CoordX() : x-hint_mu.Width()),hint_mu.CoordY());
         hint_md.Move((x-hint_md.Width()<this.CoordX() ? this.CoordX() : x-hint_md.Width()),hint_md.CoordY(),true);
        break;
     }
  }
//+------------------------------------------------------------------+

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


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

//--- Создаёт объект-графический элемент на канвасе на указанном графике и подокне
   int               CreateElement(const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h,
                                   const color clr,
                                   const uchar opacity,
                                   const bool movable,
                                   const bool activity,
                                   const bool redraw=false)
                       {
                        int id=this.GetMaxID()+1;
                        CGCnvElement *obj=new CGCnvElement(GRAPH_ELEMENT_TYPE_ELEMENT,NULL,NULL,id,0,chart_id,subwindow,descript,x,y,w,h,clr,opacity,movable,activity,redraw);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        if(res==ADD_OBJ_RET_CODE_EXIST)
                           obj.SetID(id);
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }

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

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

//--- Создаёт графический объект-форму на канвасе на указанном графике и подокне
   int               CreateForm(const long chart_id,
                                const int subwindow,
                                const string descript,
                                const int x,
                                const int y,
                                const int w,
                                const int h,
                                const color clr,
                                const uchar opacity,
                                const bool movable,
                                const bool activity,
                                const bool shadow=false,
                                const bool redraw=false)
                       {
                        int id=this.GetMaxID()+1;
                        CForm *obj=new CForm(NULL,NULL,chart_id,subwindow,descript,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(activity);
                        obj.SetMovable(movable);
                        obj.SetBackgroundColor(clr,true);
                        obj.SetBorderColor(clr,true);
                        obj.SetOpacity(opacity,false);
                        obj.SetShadow(shadow);
                        obj.DrawRectangle(0,0,obj.Width()-1,obj.Height()-1,obj.BorderColor(),obj.Opacity());
                        obj.Done();
                        obj.Erase(clr,opacity,redraw);
                        return obj.ID();
                       }

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

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


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

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

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


Запланированный функционал работает правильно.


Что дальше

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

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

К содержанию

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

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