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

DoEasy. Элементы управления (Часть 17): Отсечение невидимых участков объектов, вспомогательные WinForms-объекты кнопки со стрелками

MetaTrader 5Примеры | 9 сентября 2022, 13:27
729 8
Artyom Trishkin
Artyom Trishkin

Содержание


Концепция

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

В MQL для реализации обрезания графических элементов, созданных на основе bmp-изображения (OBJ_BITMAP_LABEL и OBJ_BITMAP), есть специальные свойства графических объектов, позволяющие отображать только часть изображения, очерченную прямоугольной областью видимости:

Для объектов OBJ_BITMAP_LABEL и OBJ_BITMAP программным путем можно установить специальный режим показа изображения. В этом режиме показывается только та часть исходного изображения, на которую накладывается прямоугольная область видимости, остальная часть картинки становится невидимой. Размеры области видимости необходимо установить с помощью свойств OBJPROP_XSIZE и OBJPROP_YSIZE. Область видимости можно "перемещать" только в пределах исходного изображения с помощью  свойств OBJPROP_XOFFSET и OBJPROP_YOFFSET.

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

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

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

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


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

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

#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)                        // Ширина рамки формы/панели/окна по умолчанию

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


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

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

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

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

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


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

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

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

   CANV_ELEMENT_PROP_ACT_RIGHT,                       // Правая граница активной зоны элемента
   CANV_ELEMENT_PROP_ACT_BOTTOM,                      // Нижняя граница активной зоны элемента
   CANV_ELEMENT_PROP_VISIBLE_AREA_X,                  // Координата X области видимости
   CANV_ELEMENT_PROP_VISIBLE_AREA_Y,                  // Координата Y области видимости
   CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,              // Ширина области видимости
   CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,             // Высота области видимости
   CANV_ELEMENT_PROP_GROUP,                           // Группа, к которой принадлежит графический элемент
   CANV_ELEMENT_PROP_ZORDER,                          // Приоритет графического объекта на получение события нажатия мышки на графике

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

  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (96)          // Общее количество целочисленных свойств
#define CANV_ELEMENT_PROP_INTEGER_SKIP  (0)           // Количество неиспользуемых в сортировке целочисленных свойств
//+------------------------------------------------------------------+


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

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

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

   SORT_BY_CANV_ELEMENT_ACT_RIGHT,                    // Сортировать по правой границе активной зоны элемента
   SORT_BY_CANV_ELEMENT_ACT_BOTTOM,                   // Сортировать по нижней границе активной зоны элемента
   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_X,               // Сортировать по координате X области видимости
   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_Y,               // Сортировать по координате Y области видимости
   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_WIDTH,           // Сортировать по ширине области видимости
   SORT_BY_CANV_ELEMENT_VISIBLE_AREA_HEIGHT,          // Сортировать по высоте области видимости
   SORT_BY_CANV_ELEMENT_GROUP,                        // Сортировать по группе, к которой принадлежит графический элемент
   SORT_BY_CANV_ELEMENT_ZORDER,                       // Сортировать по приоритету графического объекта на получение события нажатия мышки на графике

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

   SORT_BY_CANV_ELEMENT_TAB_PAGE_COLUMN,              // Сортировать по номеру столбца вкладки
   SORT_BY_CANV_ELEMENT_ALIGNMENT,                    // Сортировать по местоположению объекта внутри элемента управления
//--- Сортировка по вещественным свойствам

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

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


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

   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,               // Поле вкладки элемента управления TabControl
   MSG_GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,             // Элемент управления TabControl
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,            // Элемент управления ArrowButton
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,         // Элемент управления UpArrowButton
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,       // Элемент управления DownArrowButton
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,       // Элемент управления LeftArrowButton
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,      // Элемент управления RightArrowButton
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,    // Элемент управления UpDownArrowBox
   MSG_GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,    // Элемент управления LeftRightArrowBox
   MSG_GRAPH_OBJ_BELONG_PROGRAM,                      // Графический объект принадлежит программе
   MSG_GRAPH_OBJ_BELONG_NO_PROGRAM,                   // Графический объект не принадлежит программе

...

   MSG_CANV_ELEMENT_PROP_ACT_RIGHT,                   // Правая граница активной зоны элемента
   MSG_CANV_ELEMENT_PROP_ACT_BOTTOM,                  // Нижняя граница активной зоны элемента
   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_X,              // Координата X области видимости
   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_Y,              // Координата Y области видимости
   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,          // Ширина области видимости
   MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,         // Высота области видимости
   MSG_CANV_ELEMENT_PROP_ENABLED,                     // Флаг доступности элемента
   MSG_CANV_ELEMENT_PROP_FORE_COLOR,                  // Цвет текста по умолчанию для всех объектов элемента управления

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

   {"Поле вкладки элемента управления \"TabControl\"","Tab field of the Control element \"TabControl\""},
   {"Элемент управления \"TabControl\"","Control element \"TabControl\""},
   {"Элемент управления \"ArrowButton\"","Control element \"ArrowButton\""},
   {"Элемент управления \"UpArrowButton\"","Control element \"UpArrowButton\""},
   {"Элемент управления \"DownArrowButton\"","Control element \"DownArrowButton\""},
   {"Элемент управления \"LeftArrowButton\"","Control element \"LeftArrowButton\""},
   {"Элемент управления \"RightArrowButton\"","Control element \"RightArrowButton\""},
   {"Элемент управления \"UpDownArrowBox\"","Control element \"UpDownArrowBox\""},
   {"Элемент управления \"LeftRightArrowBox\"","Control element \"LeftRightArrowBox\""},
   {"Графический объект принадлежит программе","The graphic object belongs to the program"},
   {"Графический объект не принадлежит программе","The graphic object does not belong to the program"},

...

   {"Правая граница активной зоны элемента","Right border of the element's active area"},
   {"Нижняя граница активной зоны элемента","Bottom border of the element's active area"},
   {"Координата X области видимости","X-coordinate of object visibility area"},
   {"Координата Y области видимости","Y-coordinate of object visibility area"},
   {"Ширина области видимости","Width of object visibility area"},
   {"Высота области видимости","Height of object visibility area"},
   {"Флаг доступности элемента","Element Availability Flag"},
   {"Цвет текста по умолчанию для всех объектов элемента управления","Default text color for all objects in the control"},


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

//+------------------------------------------------------------------+
//| Возвращает тип графического объекта как string                   |
//+------------------------------------------------------------------+
string TypeGraphElementAsString(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   ushort array[];
   int total=StringToShortArray(StringSubstr(EnumToString(type),18),array);
   for(int i=0;i<total-1;i++)
     {
      if(array[i]==95)
        {
         i+=1;
         continue;
        }
      else
         array[i]+=0x20;
     }
   string txt=ShortArrayToString(array);
   StringReplace(txt,"_Wf_Base","WFBase");
   StringReplace(txt,"_Wf_","");
   StringReplace(txt,"_Obj","");
   StringReplace(txt,"_","");
   StringReplace(txt,"Groupbox","GroupBox");
   StringReplace(txt,"ButtonsUdBox","ButtonsUDBox");
   StringReplace(txt,"ButtonsLrBox","ButtonsLRBox");
   return txt;
  }
//+------------------------------------------------------------------+

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


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

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


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

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

//--- Устанавливает Приоритет графического объекта на получение события нажатия мышки на графике 
   virtual bool      SetZorder(const long value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_ZORDER,value)) || only_prop)
                          {
                           this.m_zorder=value;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
                       
//--- (1) Устанавливает, (2) возвращает X-координату левого верхнего угла прямоугольной области видимости графического объекта OBJ_BITMAP_LABEL и OBJ_BITMAP
   virtual bool      SetXOffset(const long value)
                       {
                        ::ResetLastError();
                        if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_XOFFSET,value))
                          {
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                           return false;
                          }
                        return true;
                       }
   virtual int       XOffset(void)  const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_XOFFSET);   }
//--- (1) Устанавливает, (2) возвращает Y-координату левого верхнего угла прямоугольной области видимости графического объекта OBJ_BITMAP_LABEL и OBJ_BITMAP
   virtual bool      SetYOffset(const long value)
                       {
                        ::ResetLastError();
                        if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_YOFFSET,value))
                          {
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                           return false;
                          }
                        return true;
                       }
   virtual int       YOffset(void)  const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_YOFFSET);   }
//--- (1) Устанавливает, (2) возвращает ширину объектов OBJ_LABEL (read only), OBJ_BUTTON, OBJ_CHART, OBJ_BITMAP, OBJ_BITMAP_LABEL, OBJ_EDIT, OBJ_RECTANGLE_LABEL
   virtual bool      SetXSize(const long value)
                       {
                        ::ResetLastError();
                        if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_XSIZE,value))
                          {
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                           return false;
                          }
                        return true;
                       }
   virtual int       XSize(void)    const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_XSIZE);     }
//--- (1) Устанавливает, (2) возвращает высоту объектов OBJ_LABEL (read only), OBJ_BUTTON, OBJ_CHART, OBJ_BITMAP, OBJ_BITMAP_LABEL, OBJ_EDIT, OBJ_RECTANGLE_LABEL
   virtual bool      SetYSize(const long value)
                       {
                        ::ResetLastError();
                        if(!::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_YSIZE,value))
                          {
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                           return false;
                          }
                        return true;
                       }
   virtual int       YSize(void)    const { return (int)::ObjectGetInteger(this.m_chart_id,this.m_name,OBJPROP_YSIZE);     }
                       
//--- Устанавливает видимость/невидимость объекта на всех таймфреймах
   bool              SetVisibleFlag(const bool flag,const bool only_prop)   
                       {
                        long value=(flag ? OBJ_ALL_PERIODS : OBJ_NO_PERIODS);
                        ::ResetLastError();
                        if((!only_prop && ::ObjectSetInteger(this.m_chart_id,this.m_name,OBJPROP_TIMEFRAMES,value)) || only_prop)
                          {
                           this.m_visible=flag;
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }

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

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

//+------------------------------------------------------------------+
//| Возвращает описание типа графического элемента                   |
//+------------------------------------------------------------------+
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_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)  :
      "Unknown"
     );
  }  
//+------------------------------------------------------------------+

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


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

//--- Видимость объекта на всех таймфреймах
   bool              Visible(void)                 const { return (bool)this.GetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0);                    }
   bool              SetFlagVisible(const bool flag,const bool only_prop)
                       {
                        if(!CGBaseObj::SetVisibleFlag(flag,only_prop))
                           return false;
                        this.SetProperty(GRAPH_OBJ_PROP_TIMEFRAMES,0,flag);
                        return true;
                       }
//--- Объект на заднем плане


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

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

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

      int            tab_alignment;                            // Местоположение вкладок внутри элемента управления
      int            alignment;                                // Местоположение объекта внутри элемента управления
      int            visible_area_x;                           // Координата X области видимости
      int            visible_area_y;                           // Координата Y области видимости
      int            visible_area_w;                           // Ширина области видимости
      int            visible_area_h;                           // Высота области видимости
      //--- Вещественные свойства объекта

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

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

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

//--- Устанавливает объект выше всех
   virtual void      BringToTop(void)                          { CGBaseObj::SetVisibleFlag(false,false); CGBaseObj::SetVisibleFlag(true,false);}
//--- (1) Показывает, (2) скрывает элемент
   virtual void      Show(void)                                { CGBaseObj::SetVisibleFlag(true,false);                                }
   virtual void      Hide(void)                                { CGBaseObj::SetVisibleFlag(false,false);                               }
   
//--- Приоритет графического объекта на получение события нажатия мышки на графике


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

//--- Группа графического объекта
   virtual int       Group(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_GROUP);                }
   virtual void      SetGroup(const int value)
                       {
                        CGBaseObj::SetGroup(value);
                        this.SetProperty(CANV_ELEMENT_PROP_GROUP,value);
                       }
//--- Координата X области видимости
   virtual int       XOffset(void)                       const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X);       }
   virtual bool      SetXOffset(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetXOffset(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Координата Y области видимости
   virtual int       YOffset(void)                       const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y);       }
   virtual bool      SetYOffset(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetYOffset(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Ширина области видимости
   virtual int       XSize(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH);   }
   virtual bool      SetXSize(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetXSize(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Высота области видимости
   virtual int       YSize(void)                         const { return (int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT);  }
   virtual bool      SetYSize(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
                       
//--- Координата X области видимости
   virtual int       VisibleAreaX(void)                  const { return this.XOffset();                                                }
   virtual bool      SetVisibleAreaX(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetXOffset(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Координата Y области видимости
   virtual int       VisibleAreaY(void)                  const { return this.YOffset();                                                }
   virtual bool      SetVisibleAreaY(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetYOffset(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Ширина области видимости
   virtual int       VisibleAreaWidth(void)              const { return this.XSize();                                                  }
   virtual bool      SetVisibleAreaWidth(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetXSize(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
//--- Высота области видимости
   virtual int       VisibleAreaHeight(void)             const { return this.YSize();                                                  }
   virtual bool      SetVisibleAreaHeight(const int value,const bool only_prop)
                       {
                        ::ResetLastError();
                        if((!only_prop && CGBaseObj::SetYSize(value)) || only_prop)
                          {
                           this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,value);
                           return true;
                          }
                        else
                           CMessage::ToLog(DFUN,::GetLastError(),true);
                        return false;
                       }
                       
//--- Возвращает (1) координату X, (2) правую границу, (3) координату Y, (4) нижнюю границу видимой области 
   int               CoordXVisibleArea(void)             const { return this.CoordX()+this.VisibleAreaX();                             }
   int               RightEdgeVisibleArea(void)          const { return this.CoordXVisibleArea()+this.VisibleAreaWidth();              }
   int               RightEdgeVisibleAreaRelative(void)  const { return this.VisibleAreaX()+this.VisibleAreaWidth();                   }
   int               CoordYVisibleArea(void)             const { return this.CoordY()+this.VisibleAreaY();                             }
   int               BottomEdgeVisibleArea(void)         const { return this.CoordYVisibleArea()+this.VisibleAreaHeight();             }
   int               BottomEdgeVisibleAreaRelative(void) const { return this.VisibleAreaY()+this.VisibleAreaHeight();                  }

//--- Описание графического элемента

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

Когда объект-графический элемент будет создаваться, он должен заполняться установленным для него цветом, на нём должны рисоваться нужные надписи или изображения, а затем объект должен рассчитать своё местоположение внутри контейнера и обрезать выступающие свои части за границы контейнера. Обрезка будет происходить заполнением прозрачным цветом областей, которые должны быть скрыты. Получается, что сначала нужно вызвать метод Erase(), который заполняет фон цветом, и в котором что-то ещё рисуется на объекте, а затем стереть невидимые части изображения. И всё это нужно опять же разместить в методе Erase(). Значит, первое заполнение цветом должно происходить без обрезания скрытых областей, и этот метод мы назовём EraseNoCrop(). Для обрезания скрытых областей создадим метод Crop(). И в уже имеющемся методе Erase() будем последовательно вызывать эти методы.

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

//+------------------------------------------------------------------+
//| Методы заполнения, очистки и обновления растровых данных         |
//+------------------------------------------------------------------+
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   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      Erase(const bool redraw=false);
protected:
//--- Очищает элемент с заполнением его цветом и непрозрачностью без обрезания и обновления
   virtual void      EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false);
//--- Очищает элемент заливкой градиентом без обрезания и обновления
   virtual void      EraseNoCrop(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
public:
//--- Обрезает изображение, очерченное (1) указанной, (2) ранее установленной прямоугольной областью видимости
   void              Crop(const uint coord_x,const uint coord_y,const uint width,const uint height);
   virtual void      Crop(void);
//--- Обновляет элемент
   void              Update(const bool redraw=false)           { this.m_canvas.Update(redraw);                                         }
   
//+------------------------------------------------------------------+


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

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

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

      this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight());      // Правая граница активной зоны элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom());    // Нижняя граница активной зоны элемента
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,0);                      // Координата X области видимости
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,0);                      // Координата Y области видимости
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w);                  // Ширина области видимости
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Высота области видимости

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

      this.SetProperty(CANV_ELEMENT_PROP_BELONG,ENUM_GRAPH_OBJ_BELONG::GRAPH_OBJ_BELONG_PROGRAM);  // Принадлежность графического элемента
      this.SetProperty(CANV_ELEMENT_PROP_ZORDER,0);                              // Приоритет графического объекта на получение события нажатия мышки на графике

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

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

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

      this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.ActiveAreaRight());      // Правая граница активной зоны элемента
      this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.ActiveAreaBottom());    // Нижняя граница активной зоны элемента
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,0);                      // Координата X области видимости
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,0);                      // Координата Y области видимости
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,w);                  // Ширина области видимости
      this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,h);                 // Высота области видимости

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

      this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP);                   // Местоположение вкладок внутри элемента управления
      this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,CANV_ELEMENT_ALIGNMENT_TOP);                       // Местоположение объекта внутри элемента управления

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

      this.SetProperty(CANV_ELEMENT_PROP_TEXT,"");                                                    // Текст графического элемента
      this.SetProperty(CANV_ELEMENT_PROP_DESCRIPTION,descript);                                       // Описание графического элемента
      this.SetVisibleFlag(false,false);
     }
   else
     {
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),"\"",this.TypeElementDescription(element_type),"\" ",this.NameObj());
     }
  }
//+------------------------------------------------------------------+


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

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

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

   this.m_struct_obj.coord_act_y=(int)this.GetProperty(CANV_ELEMENT_PROP_COORD_ACT_Y);             // Y-координата активной зоны элемента
   this.m_struct_obj.coord_act_right=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_RIGHT);           // Правая граница активной зоны элемента
   this.m_struct_obj.coord_act_bottom=(int)this.GetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM);         // Нижняя граница активной зоны элемента
   this.m_struct_obj.visible_area_x=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X);       // Координата X области видимости
   this.m_struct_obj.visible_area_y=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y);       // Координата Y области видимости
   this.m_struct_obj.visible_area_w=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH);   // Ширина области видимости
   this.m_struct_obj.visible_area_h=(int)this.GetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT);  // Высота области видимости
   this.m_struct_obj.zorder=this.GetProperty(CANV_ELEMENT_PROP_ZORDER);                            // Приоритет графического объекта на получение события нажатия мышки на графике
   this.m_struct_obj.enabled=(bool)this.GetProperty(CANV_ELEMENT_PROP_ENABLED);                    // Флаг доступности элемента

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

   this.m_struct_obj.tab_alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT);                              // Местоположение вкладок внутри элемента управления
   this.m_struct_obj.alignment=(int)this.GetProperty(CANV_ELEMENT_PROP_ALIGNMENT);                                      // Местоположение объекта внутри элемента управления
//--- Сохранение вещественных свойств

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


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

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

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

   this.SetProperty(CANV_ELEMENT_PROP_ACT_RIGHT,this.m_struct_obj.coord_act_right);                // Правая граница активной зоны элемента
   this.SetProperty(CANV_ELEMENT_PROP_ACT_BOTTOM,this.m_struct_obj.coord_act_bottom);              // Нижняя граница активной зоны элемента
   this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_X,this.m_struct_obj.visible_area_x);            // Координата X области видимости
   this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_Y,this.m_struct_obj.visible_area_y);            // Координата Y области видимости
   this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH,this.m_struct_obj.visible_area_w);        // Ширина области видимости
   this.SetProperty(CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT,this.m_struct_obj.visible_area_h);       // Высота области видимости
   this.SetProperty(CANV_ELEMENT_PROP_ZORDER,this.m_struct_obj.zorder);                            // Приоритет графического объекта на получение события нажатия мышки на графике
   this.SetProperty(CANV_ELEMENT_PROP_ENABLED,this.m_struct_obj.enabled);                          // Флаг доступности элемента

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

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

   this.SetProperty(CANV_ELEMENT_PROP_TAB_ALIGNMENT,this.m_struct_obj.tab_alignment);                             // Местоположение вкладок внутри элемента управления
   this.SetProperty(CANV_ELEMENT_PROP_ALIGNMENT,this.m_struct_obj.alignment);                                     // Местоположение объекта внутри элемента управления
//--- Сохранение вещественных свойств

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


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

//+------------------------------------------------------------------+
//| Устанавливает новую ширину                                       |
//+------------------------------------------------------------------+
bool CGCnvElement::SetWidth(const int width)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_WIDTH)==width)
      return true;
   if(!this.m_canvas.Resize(width,this.m_canvas.Height()))
     {
      CMessage::ToLog(DFUN+this.TypeElementDescription()+": width="+(string)width+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_WIDTH);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_WIDTH,width);
   this.SetVisibleAreaX(0,true);
   this.SetVisibleAreaWidth(width,true);
   return true;
  }
//+------------------------------------------------------------------+
//| Устанавливает новую высоту                                       |
//+------------------------------------------------------------------+
bool CGCnvElement::SetHeight(const int height)
  {
   if(this.GetProperty(CANV_ELEMENT_PROP_HEIGHT)==height)
      return true;
   if(!this.m_canvas.Resize(this.m_canvas.Width(),height))
     {
      CMessage::ToLog(DFUN+this.TypeElementDescription()+": height="+(string)height+": ",MSG_CANV_ELEMENT_ERR_FAILED_SET_HEIGHT);
      return false;
     }
   this.SetProperty(CANV_ELEMENT_PROP_HEIGHT,height);
   this.SetVisibleAreaY(0,true);
   this.SetVisibleAreaHeight(height,true);
   return true;
  }
//+------------------------------------------------------------------+

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

Теперь методы Erase() будут такими:

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

Сначала вызывается метод EraseNoCrop(), в котором производится очистка элемента указанным цветом, с запретом обновления, затем вызывается метод Crop(), выполняющий обрезание скрытых областей, и далее обновляется канвас с указанным флагом обновления графика.


Методы заливки канваса цветом без обрезания скрытых областей:

//+------------------------------------------------------------------+
//| Очищает элемент с заполнением его цветом и непрозрачностью       |
//| без обрезания и с обновлением графика по флагу                   |
//+------------------------------------------------------------------+
void CGCnvElement::EraseNoCrop(const color colour,const uchar opacity,const bool redraw=false)
  {
   color arr[1];
   arr[0]=colour;
   this.SaveColorsBG(arr);
   this.m_canvas.Erase(::ColorToARGB(colour,opacity));
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Очищает элемент заливкой градиентом без обрезания                |
//| и с обновлением графика по флагу                                 |
//+------------------------------------------------------------------+
void CGCnvElement::EraseNoCrop(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false)
  {
//--- Устанавливаем флаги вертикальной и циклической градиентной заливки
   this.m_gradient_v=vgradient;
   this.m_gradient_c=cycle;
//--- Проверяем размер массива цветов
   int size=::ArraySize(colors);
//--- Если цветов в массиве меньше двух
   if(size<2)
     {
      //--- если пустой массив, то полностью стираем фон и уходим
      if(size==0)
        {
         this.Erase(redraw);
         return;
        }
      //--- если один цвет, то заливаем фон этим цветом и непрозрачностью и уходим
      this.EraseNoCrop(colors[0],opacity,redraw);
      return;
     }
//--- Объявляем массив-приёмник
   color out[];
//--- В зависимости от направления заливки (вертикально/горизонтально) устанавливаем размер градиента
   int total=(vgradient ? this.Height() : this.Width());
//--- и получаем в массив-приёмник набор цветов
   CColors::Gradient(colors,out,total,cycle);
   total=::ArraySize(out);
//--- В цикле по количеству цветов в массиве
   for(int i=0;i<total;i++)
     {
      //--- в зависимости от направления заливки
      switch(vgradient)
        {
         //--- Горизонтальный градиент - рисуем вертикальные отрезки прямых слева-направо с цветом из массива
         case false :
            DrawLineVertical(i,0,this.Height()-1,out[i],opacity);
           break;
         //--- Вертикальный градиент - рисуем горизонтальные отрезки прямых сверху-вниз с цветом из массива
         default:
            DrawLineHorizontal(0,this.Width()-1,i,out[i],opacity);
           break;
        }
     }
//--- Сохраняем массив цветов фона
   this.SaveColorsBG(colors);
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Обрезает изображение, очерченное прямоугольной областью видимости|
//+------------------------------------------------------------------+
void CGCnvElement::Crop(const uint coord_x,const uint coord_y,const uint width,const uint height)
  {
//--- Если переданные координаты и размеры области видимости совпадают с размерами объекта - уходим
   if(coord_x==0 && coord_y==0 && width==this.Width() && height==this.Height())
      return;
//--- Устанавливаем в свойства объекта координаты и размеры области видимости
   this.SetVisibleAreaX(coord_x,true);
   this.SetVisibleAreaY(coord_y,true);
   this.SetVisibleAreaWidth(width,true);
   this.SetVisibleAreaHeight(height,true);
//--- Если объект в текущем состоянии ещё не сохранялся -
//--- сохраняем в массив его растровое изображение для последующего восстановления из массива
   if(::ArraySize(this.m_duplicate_res)==0)
      this.ResourceStamp(DFUN);
//--- В цикле по строкам изображения графического объекта
   for(int y=0;y<this.Height();y++)
     {
      //--- проходим по каждому пикселю текущей строки
      for(int x=0;x<this.Width();x++)
        {
         //--- Если строка и её пиксель находится в области видимости - пропускаем пиксель
         if(y>=this.VisibleAreaY() && y<=this.BottomEdgeVisibleAreaRelative() && x>=this.VisibleAreaX() && x<=this.RightEdgeVisibleAreaRelative())
            continue;
         //--- Если пиксель строки находится за пределами области видимости - устанавливаем для него прозрачный цвет
         this.SetPixel(x,y,CLR_CANV_NULL,0);
        }
     }
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Обрезает изображение, очерченное рассчитываемой                  |
//| прямоугольной областью видимости                                 |
//+------------------------------------------------------------------+
void CGCnvElement::Crop(void)
  {
//--- Получаем указатель на базовый объект
   CGCnvElement *base=this.GetBase();
//--- Если у объекта нет базового, к которому он прикреплён, то и обрезать скрытые области не нужно - уходим
   if(base==NULL)
      return;
//--- Задаём начальные координаты и размеры области видимости во весь объект
   int vis_x=0;
   int vis_y=0;
   int vis_w=this.Width();
   int vis_h=this.Height();
//--- Задаём размеры областей сверху, снизу, слева и справа, которые выходят за пределы контейнера
   int crop_top=0;
   int crop_bottom=0;
   int crop_left=0;
   int crop_right=0;
//--- Рассчитываем границы области контейнера, внутри которой объект полностью виден
   int top=fmax(base.CoordY()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_TOP),base.CoordYVisibleArea());
   int bottom=fmin(base.BottomEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_BOTTOM),base.BottomEdgeVisibleArea()+1);
   int left=fmax(base.CoordX()+(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_LEFT),base.CoordXVisibleArea());
   int right=fmin(base.RightEdge()-(int)base.GetProperty(CANV_ELEMENT_PROP_BORDER_SIZE_RIGHT),base.RightEdgeVisibleArea()+1);
//--- Рассчитываем величины областей сверху, снизу, слева и справа, на которые объект выходит
//--- за пределы границ области контейнера, внутри которой объект полностью виден
   crop_top=this.CoordY()-top;
   if(crop_top<0)
      vis_y=-crop_top;
   crop_bottom=bottom-this.BottomEdge()-1;
   if(crop_bottom<0)
      vis_h=this.Height()+crop_bottom-vis_y;
   crop_left=this.CoordX()-left;
   if(crop_left<0)
      vis_x=-crop_left;
   crop_right=right-this.RightEdge()-1;
   if(crop_right<0)
      vis_w=this.Width()+crop_right-vis_x;
//--- Если есть области, которые необходимо скрыть - вызываем метод обрезания с рассчитанными размерами области видимости объекта
   if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0)
      this.Crop(vis_x,vis_y,vis_w,vis_h);
  }
//+------------------------------------------------------------------+

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


В файле класса объекта-тени \MQL5\Include\DoEasy\Objects\Graph\ShadowObj.mqh сделаем некоторые доработки.

В конструкторе класса при создании объекта установим ему флаг видимости как "скрыт":

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CShadowObj::CShadowObj(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,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();
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Рисует тень объекта                                              |
//+------------------------------------------------------------------+
void CShadowObj::Draw(const int shift_x,const int shift_y,const uchar blur_value,const bool redraw)
  {
   if(!this.IsVisible())
      return;
//--- Устанавливаем в переменные значения смещения тени по осям X и Y
   this.SetCoordXRelative(shift_x);
   this.SetCoordYRelative(shift_y);
//--- Рассчитываем ширину и высоту рисуемого прямоугольника
   int w=this.Width()-OUTER_AREA_SIZE*2;
   int h=this.Height()-OUTER_AREA_SIZE*2;
//--- Рисуем закрашенный прямоугольник с рассчитанными размерами
   this.DrawShadowFigureRect(w,h);
//--- Рассчитываем радиус размытия, который не может быть больше четверти размера константы OUTER_AREA_SIZE
   this.m_blur=(blur_value>OUTER_AREA_SIZE/4 ? OUTER_AREA_SIZE/4 : blur_value);
//--- Если размыть фигуру не удалось - уходим из метода (ошибку в журнал выведет GaussianBlur())
   if(!this.GaussianBlur(this.m_blur))
      return;
//--- Смещаем объект тени на указанные в аргументах метода смещения по X и Y и обновляем канвас
   CGCnvElement::Move(this.CoordX()+this.CoordXRelative(),this.CoordY()+this.CoordYRelative(),redraw);
   CGCnvElement::Update(redraw);
  }
//+------------------------------------------------------------------+


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

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


Так как теперь тень рисуется только, если для неё установлен флаг видимости, то в методе рисования тени поменяем местами отрисовку тени и установку ей флага видимости:

//+------------------------------------------------------------------+
//| Рисует тень                                                      |
//+------------------------------------------------------------------+
void CForm::DrawShadow(const int shift_x,const int shift_y,const color colour,const uchar opacity=127,const uchar blur=DEF_SHADOW_BLUR)
  {
//--- Если флаг тени выключен - уходим
   if(!this.m_shadow)
      return;
//--- Если объект тени не создан - создадим его
   if(this.m_shadow_obj==NULL)
      this.CreateShadowObj(colour,opacity);
//--- Если объект тени существует, рисуем на нём тень,
//--- устанавливаем флаг видимости объекта-тени и
//--- перемещаем объект-форму на передний план
   if(this.m_shadow_obj!=NULL)
     {
      this.m_shadow_obj.SetVisibleFlag(true,false);
      this.m_shadow_obj.Draw(shift_x,shift_y,blur,true);
      this.BringToTop();
     }
  }
//+------------------------------------------------------------------+

Ранее вызов этих методов был в обратной последовательности, и тень не рисовалась.


В файле класса базового объекта всех WinForms-объектов \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh доработаем методы Erase():

//+------------------------------------------------------------------+
//| Очищает элемент с заполнением его цветом и непрозрачностью       |
//+------------------------------------------------------------------+
void CWinFormBase::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Закрашиваем элемент с указанным цветом и флагом необходимости перерисовки
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- Если у объекта есть рамка - рисуем её
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle());
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Очищает элемент заливкой градиентом                              |
//+------------------------------------------------------------------+
void CWinFormBase::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.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),this.Opacity(),this.BorderStyle());
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Теперь мы сначала вызываем метод EraseNoCrop() объекта-графического элемента, затем рисуем рамку и обрезаем скрытые области.


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

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

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

      property==CANV_ELEMENT_PROP_ACT_RIGHT                    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_RIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_ACT_BOTTOM                   ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ACT_BOTTOM)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_VISIBLE_AREA_X               ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_X)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_VISIBLE_AREA_Y               ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_Y)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH           ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_WIDTH)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT          ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_VISIBLE_AREA_HEIGHT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_GROUP                        ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_GROUP)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_ZORDER                       ?  CMessage::Text(MSG_GRAPH_OBJ_PROP_ZORDER)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :

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

      property==CANV_ELEMENT_PROP_TAB_PAGE_COLUMN              ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_TAB_PAGE_COLUMN)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+(string)this.GetProperty(property)
         )  :
      property==CANV_ELEMENT_PROP_ALIGNMENT                    ?  CMessage::Text(MSG_CANV_ELEMENT_PROP_ALIGNMENT)+
         (only_prop ? "" : !this.SupportProperty(property)     ?  ": "+CMessage::Text(MSG_LIB_PROP_NOT_SUPPORTED) :
          ": "+AlignmentDescription((ENUM_CANV_ELEMENT_ALIGNMENT)this.GetProperty(property))
         )  :
      ""
     );
  }
//+------------------------------------------------------------------+

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


В файле класса базового объекта стандартных элементов управления \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CommonBase.mqh доработаем методы Erase() в соответствии с новой концепцией их построения:

//+------------------------------------------------------------------+
//| Очищает элемент с заполнением его цветом и непрозрачностью       |
//+------------------------------------------------------------------+
void CCommonBase::Erase(const color colour,const uchar opacity,const bool redraw=false)
  {
//--- Закрашиваем элемент с указанным цветом и флагом необходимости перерисовки
   CGCnvElement::EraseNoCrop(colour,opacity,false);
//--- Если у объекта есть рамка - рисуем её
   if(this.BorderStyle()!=FRAME_STYLE_NONE)
      this.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle());
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Очищает элемент заливкой градиентом                              |
//+------------------------------------------------------------------+
void CCommonBase::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.DrawFormFrame(this.BorderSizeTop(),this.BorderSizeBottom(),this.BorderSizeLeft(),this.BorderSizeRight(),this.BorderColor(),255,this.BorderStyle());
//--- Обновляем элемент с указанным флагом необходимости перерисовки
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Здесь: сначала вызываем метод EraseNoCrop() объекта-графического элемента, затем рисуем рамку и обрезаем скрытые области.


В файле класса WinForms-объекта CheckBox \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\CheckBox.mqh доработаем метод Redraw():

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CCheckBox::Redraw(bool redraw)
  {
//--- Заполняем объект цветом фона с полной прозрачностью
   this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),true);
//--- Устанавливаем скорректированные координаты текста относительно флажка проверки
   this.SetCorrectTextCoords();
//--- Рисуем текст и флажок выбора в установленных координатах объекта и точкой привязки и обновляем объект 
   this.Text(this.m_text_x,this.m_text_y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.ShowControlFlag(this.CheckState());
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

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


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

//--- Устанавливает текст элемента
   virtual void      SetText(const string text)
                       {
                        CWinFormBase::SetText(text);
                        if(this.AutoSize())
                           this.AutoSetWH();
                        this.Crop();
                       }


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

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CLabel::Redraw(bool redraw)
  {
//--- Заполняем объект цветом фона с полной прозрачностью
   this.EraseNoCrop(this.BackgroundColor(),0,redraw);
//--- Объявляем переменные для координат X и Y и устанавливаем их значения в зависимости от выравнивания текста
   int x=0,y=0;
   this.SetTextParamsByAlign(x,y);
//--- Рисуем текст в установленных координатах объекта и точкой привязки текста и обновляем объект 
   this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CButton::Redraw(bool redraw)
  {
//--- Заполняем объект цветом фона
   this.EraseNoCrop(this.BackgroundColor(),this.Opacity(),redraw);
//--- Объявляем переменные для координат X и Y и устанавливаем их значения в зависимости от выравнивания текста
   int x=0,y=0;
   CLabel::SetTextParamsByAlign(x,y);
//--- Рисуем текст в установленных координатах объекта и точкой привязки текста и обновляем объект 
   this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+


Классы вспомогательных объектов-кнопок со стрелками

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

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

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

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

//+------------------------------------------------------------------+
//|                                                  ArrowButton.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 "Common Controls\Button.mqh"
//+------------------------------------------------------------------+
//| Класс объекта Arrow Button элементов управления WForms           |
//+------------------------------------------------------------------+
class CArrowButton : public CButton
  {
  }


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

//+------------------------------------------------------------------+
//| Класс объекта Arrow Button элементов управления WForms           |
//+------------------------------------------------------------------+
class CArrowButton : public CButton
  {
private:
   color             m_arrow_color;                      // Цвет стрелки
protected:
   //--- Рисует стрелку
   virtual void      DrawArrow(void){return;}
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CArrowButton(const ENUM_GRAPH_ELEMENT_TYPE type,
                                  const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
public:
//--- (1) Устанавливает, (2) возвращает цвет стрелки
   void              SetArrowColor(const color clr)      { this.m_arrow_color=clr;     }
   color             ArrowColor(void)              const { return this.m_arrow_color;  }
//--- Конструктор
                     CArrowButton(const long chart_id,
                                  const int subwindow,
                                  const string descript,
                                  const int x,
                                  const int y,
                                  const int w,
                                  const int h);
//--- Перерисовывает объект
   virtual void      Redraw(bool redraw);
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   virtual void      Erase(const color colour,const uchar opacity,const bool redraw=false);
//--- Очищает элемент заливкой градиентом
   virtual void      Erase(color &colors[],const uchar opacity,const bool vgradient,const bool cycle,const bool redraw=false);
//--- Рисует рамку кнопки
   virtual void      DrawFrame(void);
  };
//+------------------------------------------------------------------+

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

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

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

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

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

После создания объекта все эти параметры (кроме типов объекта) можно будет менять.

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CArrowButton::CArrowButton(const long chart_id,
                           const int subwindow,
                           const string descript,
                           const int x,
                           const int y,
                           const int w,
                           const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetPaddingAll(0);
   this.SetMarginAll(0);
   this.SetBorderSizeAll(1);
   this.SetArrowColor(CLR_DEF_FORE_COLOR);
  }
//+------------------------------------------------------------------+


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

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

Здесь просто вызывается метод Erase(), в котором и происходит перерисовка:

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

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


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

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

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


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

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

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

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


Объект-кнопка со стрелкой влево.

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

//+------------------------------------------------------------------+
//|                                              ArrowLeftButton.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 "ArrowButton.mqh"
//+------------------------------------------------------------------+
//| Класс объекта Left Arrow Button элементов управления WForms      |
//+------------------------------------------------------------------+
class CArrowLeftButton : public CArrowButton
  {
  }


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

//+------------------------------------------------------------------+
//| Класс объекта Left Arrow Button элементов управления WForms      |
//+------------------------------------------------------------------+
class CArrowLeftButton : public CArrowButton
  {
private:

protected:
   //--- Рисует стрелку
   virtual void      DrawArrow(void);
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CArrowLeftButton(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const long chart_id,
                                      const int subwindow,
                                      const string descript,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h);
public:
//--- Конструктор
                     CArrowLeftButton(const long chart_id,
                                      const int subwindow,
                                      const string descript,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h);
  };
//+------------------------------------------------------------------+


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

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


Метод для рисования стрелки:

//+------------------------------------------------------------------+
//| Рисует стрелку                                                   |
//+------------------------------------------------------------------+
void CArrowLeftButton::DrawArrow(void)
  {
//--- Создаём массивы координат X и Y для рисования треугольника
   double x=(double)this.Width()/2;
   double y=(double)this.Height()/2;
   double w=(double)this.Width();
   double h=(double)this.Height();
//--- Координаты рассчитываем как double-значения и записываем их как целочисленные значения в массивы
   int array_x[]={int(w*0.7), int(w*0.7), int(w*0.3)};
   int array_y[]={int(h*0.3), int(h*0.7), int(h*0.5)};
//--- Рисуем закрашенный треугольник, а поверх него - сглаженный
   this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
   this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
  }
//+------------------------------------------------------------------+

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

Рассмотрим их целиком без пояснений, так как вышеописанный класс полностью идентичен остальным, и находятся все эти классы в одной и той же папке библиотеки  \MQL5\Include\DoEasy\Objects\Graph\WForms\.


Класс объекта-кнопки со стрелкой вправо в файле ArrowRightButton.mqh:

//+------------------------------------------------------------------+
//|                                             ArrowRightButton.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 "ArrowButton.mqh"
//+------------------------------------------------------------------+
//| Класс объекта Right Arrow Button элементов управления WForms     |
//+------------------------------------------------------------------+
class CArrowRightButton : public CArrowButton
  {
private:

protected:
   //--- Рисует стрелку
   virtual void      DrawArrow(void);
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CArrowRightButton(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const long chart_id,
                                       const int subwindow,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h);
public:
//--- Конструктор
                     CArrowRightButton(const long chart_id,
                                       const int subwindow,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h);
  };
//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CArrowRightButton::CArrowRightButton(const ENUM_GRAPH_ELEMENT_TYPE type,
                                   const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h) : CArrowButton(type,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
  }
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CArrowRightButton::CArrowRightButton(const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT);
  }
//+------------------------------------------------------------------+
//| Рисует стрелку                                                   |
//+------------------------------------------------------------------+
void CArrowRightButton::DrawArrow(void)
  {
//--- Создаём массивы координат X и Y для рисования треугольника
   double x=(double)this.Width()/2;
   double y=(double)this.Height()/2;
   double w=(double)this.Width();
   double h=(double)this.Height();
//--- Координаты рассчитываем как double-значения и записываем их как целочисленные значения в массивы
   int array_x[]={int(w*0.3), int(w*0.7), int(w*0.3)};
   int array_y[]={int(h*0.3), int(h*0.5), int(h*0.7)};
//--- Рисуем закрашенный треугольник, а поверх него - сглаженный
   this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
   this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
  }
//+------------------------------------------------------------------+


Класс объекта-кнопки со стрелкой вверх в файле ArrowUpButton.mqh:

//+------------------------------------------------------------------+
//|                                                ArrowUpButton.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 "ArrowButton.mqh"
//+------------------------------------------------------------------+
//| Класс объекта Up Arrow Button элементов управления WForms        |
//+------------------------------------------------------------------+
class CArrowUpButton : public CArrowButton
  {
private:

protected:
   //--- Рисует стрелку
   virtual void      DrawArrow(void);
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CArrowUpButton(const ENUM_GRAPH_ELEMENT_TYPE type,
                                    const long chart_id,
                                    const int subwindow,
                                    const string descript,
                                    const int x,
                                    const int y,
                                    const int w,
                                    const int h);
public:
//--- Конструктор
                     CArrowUpButton(const long chart_id,
                                    const int subwindow,
                                    const string descript,
                                    const int x,
                                    const int y,
                                    const int w,
                                    const int h);
  };
//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CArrowUpButton::CArrowUpButton(const ENUM_GRAPH_ELEMENT_TYPE type,
                               const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h) : CArrowButton(type,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
  }
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CArrowUpButton::CArrowUpButton(const long chart_id,
                               const int subwindow,
                               const string descript,
                               const int x,
                               const int y,
                               const int w,
                               const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP);
  }
//+------------------------------------------------------------------+
//| Рисует стрелку                                                   |
//+------------------------------------------------------------------+
void CArrowUpButton::DrawArrow(void)
  {
//--- Создаём массивы координат X и Y для рисования треугольника
   double x=(double)this.Width()/2;
   double y=(double)this.Height()/2;
   double w=(double)this.Width();
   double h=(double)this.Height();
//--- Координаты рассчитываем как double-значения и записываем их как целочисленные значения в массивы
   int array_x[]={int(w*0.3), int(w*0.5), int(w*0.7)};
   int array_y[]={int(h*0.7), int(h*0.3), int(h*0.7)};
//--- Рисуем закрашенный треугольник, а поверх него - сглаженный
   this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
   this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
  }
//+------------------------------------------------------------------+


Класс объекта-кнопки со стрелкой вниз в файле ArrowDownButton.mqh:

//+------------------------------------------------------------------+
//|                                              ArrowDownButton.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 "ArrowButton.mqh"
//+------------------------------------------------------------------+
//| Класс объекта Down Arrow Button элементов управления WForms      |
//+------------------------------------------------------------------+
class CArrowDownButton : public CArrowButton
  {
private:

protected:
   //--- Рисует стрелку
   virtual void      DrawArrow(void);
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CArrowDownButton(const ENUM_GRAPH_ELEMENT_TYPE type,
                                      const long chart_id,
                                      const int subwindow,
                                      const string descript,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h);
public:
//--- Конструктор
                     CArrowDownButton(const long chart_id,
                                      const int subwindow,
                                      const string descript,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h);
  };
//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CArrowDownButton::CArrowDownButton(const ENUM_GRAPH_ELEMENT_TYPE type,
                                   const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h) : CArrowButton(type,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
  }
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CArrowDownButton::CArrowDownButton(const long chart_id,
                                   const int subwindow,
                                   const string descript,
                                   const int x,
                                   const int y,
                                   const int w,
                                   const int h) : CArrowButton(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN);
  }
//+------------------------------------------------------------------+
//| Рисует стрелку                                                   |
//+------------------------------------------------------------------+
void CArrowDownButton::DrawArrow(void)
  {
//--- Создаём массивы координат X и Y для рисования треугольника
   double x=(double)this.Width()/2;
   double y=(double)this.Height()/2;
   double w=(double)this.Width();
   double h=(double)this.Height();
//--- Координаты рассчитываем как double-значения и записываем их как целочисленные значения в массивы
   int array_x[]={int(w*0.3), int(w*0.5), int(w*0.7)};
   int array_y[]={int(h*0.3), int(h*0.7), int(h*0.3)};
//--- Рисуем закрашенный треугольник, а поверх него - сглаженный
   this.DrawTriangleFill(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
   this.DrawTriangleWu(array_x[0],array_y[0],array_x[1],array_y[1],array_x[2],array_y[2],this.ArrowColor());
  }
//+------------------------------------------------------------------+


Все эти классы идентичны. Можно заметить разницу лишь в типах объектов, устанавливаемых в конструкторах классов и разницу в значениях координат вершин в методах DrawArrow().


На основе созданных классов объектов-кнопок со стрелками создадим ещё два вспомогательных класса. Каждый из них будет иметь контейнер, к которому будут прикреплены по две кнопки. В первом классе — кнопка влево и кнопка вправо, расположенные горизонтально, во втором классе — кнопка вверх и кнопка вниз, расположенные вертикально.

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

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


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

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

protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CArrowLeftRightBox(const ENUM_GRAPH_ELEMENT_TYPE type,
                                        const long chart_id,
                                        const int subwindow,
                                        const string descript,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h);
public:
//--- Возвращает указатель на кнопку со стрелкой (1) вверх, (2) вниз
   CArrowLeftButton *GetArrowUpButton(void)     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0);    }
   CArrowRightButton*GetArrowDownButton(void)   { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,0);   }
//--- Конструктор
                     CArrowLeftRightBox(const long chart_id,
                                        const int subwindow,
                                        const string descript,
                                        const int x,
                                        const int y,
                                        const int w,
                                        const int h);
  };
//+------------------------------------------------------------------+


Рассмотрим реализацию объявленных методов.

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

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CArrowLeftRightBox::CArrowLeftRightBox(const ENUM_GRAPH_ELEMENT_TYPE type,
                                       const long chart_id,
                                       const int subwindow,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h) : CContainer(type,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE));
  }
//+------------------------------------------------------------------+

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


Параметрический конструктор с указанием идентификатора чарта и подокна:

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CArrowLeftRightBox::CArrowLeftRightBox(const long chart_id,
                                       const int subwindow,
                                       const string descript,
                                       const int x,
                                       const int y,
                                       const int w,
                                       const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE));
  }
//+------------------------------------------------------------------+

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


Метод, создающий объекты ArrowButton Left и Right:

//+------------------------------------------------------------------+
//| Создаёт объекты ArrowButton Left и Right                         |
//+------------------------------------------------------------------+
void CArrowLeftRightBox::CreateArrowButtons(const int width,const int height)
  {
//--- Рассчитаем ширину объекта из ширины двух кнопок плюс размеры рамки слева и справа
//--- и высоту объекта из высоты кнопки плюс размеры рамки сверху и снизу
   int w=width*2+this.BorderSizeLeft()+this.BorderSizeRight();
   int h=height+this.BorderSizeTop()+this.BorderSizeBottom();
//--- Если полученные ширина или высота больше ширины или высоты объекта - изменяем его размеры
   if(w>this.Width() || h>this.Height())
      this.Resize((w>this.Width() ? w : this.Width()),(h>this.Height() ? h : this.Height()),false);
//--- Создаём две кнопки рядом друг с другом, начиная с координаты 0 : 0 контейнера
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,0,0,width,height,clrNONE,255,true,false);
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,width,0,width,height,clrNONE,255,true,false);
  }
//+------------------------------------------------------------------+

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

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

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

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


Класс для создания контейнера с двумя вертикально расположенными кнопками идентичен вышерассмотренному.

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

//+------------------------------------------------------------------+
//|                                               ArrowUpDownBox.mqh |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2022, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
#property strict    // Нужно для mql4
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Containers\Panel.mqh"
//+------------------------------------------------------------------+
//| Класс объекта ArrowUpDownBox элементов управления WForms         |
//+------------------------------------------------------------------+
class CArrowUpDownBox : public CContainer
  {
private:
//--- Создаёт новый графический объект
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string descript,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
//--- Создаёт объекты ArrowButton Up и Down
   void              CreateArrowButtons(const int width,const int height);

protected:
//--- Защищённый конструктор с указанием типа объекта, идентификатора чарта и подокна
                     CArrowUpDownBox(const ENUM_GRAPH_ELEMENT_TYPE type,
                                     const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
public:
//--- Возвращает указатель на кнопку со стрелкой (1) вверх, (2) вниз
   CArrowUpButton   *GetArrowUpButton(void)     { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,0);   }
   CArrowDownButton *GetArrowDownButton(void)   { return this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0); }
//--- Конструктор
                     CArrowUpDownBox(const long chart_id,
                                     const int subwindow,
                                     const string descript,
                                     const int x,
                                     const int y,
                                     const int w,
                                     const int h);
  };
//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CArrowUpDownBox::CArrowUpDownBox(const ENUM_GRAPH_ELEMENT_TYPE type,
                                 const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h) : CContainer(type,chart_id,subwindow,descript,x,y,w,h)
  {
//--- Установим объекту указанный тип графического элемента, а тип объекта библиотеки - как тип этого объекта
   this.SetTypeElement(type);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE));
  }
//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CArrowUpDownBox::CArrowUpDownBox(const long chart_id,
                                 const int subwindow,
                                 const string descript,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   this.SetBorderColor(CLR_DEF_BORDER_COLOR,true);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.CreateArrowButtons((w<DEF_ARROW_BUTTON_SIZE ? w : DEF_ARROW_BUTTON_SIZE),(h<DEF_ARROW_BUTTON_SIZE ? h : DEF_ARROW_BUTTON_SIZE));
  }
//+------------------------------------------------------------------+
//| Создаёт объекты ArrowButton Up и Down                            |
//+------------------------------------------------------------------+
void CArrowUpDownBox::CreateArrowButtons(const int width,const int height)
  {
//--- Рассчитаем ширину объекта из ширины кнопки плюс размеры рамки слева и справа
//--- и высоту объекта из высоты двух кнопок плюс размеры рамки сверху и снизу
   int w=width+this.BorderSizeLeft()+this.BorderSizeRight();
   int h=height*2+this.BorderSizeTop()+this.BorderSizeBottom();
//--- Если полученные ширина или высота больше ширины или высоты объекта - изменяем его размеры
   if(w>this.Width() || h>this.Height())
      this.Resize((w>this.Width() ? w : this.Width()),(h>this.Height() ? h : this.Height()),false);
//--- Создаём две кнопки одна над другой, начиная с координаты 0 : 0 контейнера
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,0,0,width,height,clrNONE,255,true,false);
   this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,0,height,width,height,clrNONE,255,true,false);
  }
//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
CGCnvElement *CArrowUpDownBox::CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                                const int obj_num,
                                                const string descript,
                                                const int x,
                                                const int y,
                                                const int w,
                                                const int h,
                                                const color colour,
                                                const uchar opacity,
                                                const bool movable,
                                                const bool activity)
  {
   CGCnvElement *element=NULL;
   switch(type)
     {
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
         element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
         element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      default:
        break;
     }
   if(element==NULL)
      ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(type));
   return element;
  }
//+------------------------------------------------------------------+

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

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


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

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

public:
//--- Возвращает размеры и координаты рабочей области
   int               WidthWorkspace(void)          const
                       {
                        return this.Width()-::fmax(this.BorderSizeLeft(),this.PaddingLeft())-::fmax(this.BorderSizeRight(),this.PaddingRight());
                       }
   int               HeightWorkspace(void)         const
                       {
                        return this.Height()-::fmax(this.BorderSizeTop(),this.PaddingTop())-::fmax(this.BorderSizeBottom(),this.PaddingBottom());
                       }
   int               CoordXWorkspace(void)         const
                       {
                        return this.CoordX()+::fmax(this.BorderSizeLeft(),this.PaddingLeft());
                       }
   int               CoordYWorkspace(void)         const
                       {
                        return this.CoordY()+::fmax(this.BorderSizeTop(),this.PaddingTop());
                       }
   int               RightEdgeWorkspace(void)      const
                       {
                        return this.RightEdge()-::fmax(this.BorderSizeRight(),this.PaddingRight());
                       }
   int               BottomEdgeWorkspace(void)     const
                       {
                        return this.BottomEdge()-::fmax(this.BorderSizeBottom(),this.PaddingBottom());
                       }

//--- Возвращает список прикреплённых WinForms-объектов с (1) любым, (2) указанным типом WinForms-объекта от базового и выше


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

   virtual void      SetBorderSizeBottom(const uint value)
                       {
                        CForm::SetBorderSizeBottom(value);
                        if(this.PaddingBottom()<this.BorderSizeBottom())
                           this.SetPaddingBottom(this.BorderSizeBottom());
                       }
                       
//--- Обрезает изображение, очерченное установленной прямоугольной областью видимости
   virtual void      Crop(void);

protected:

Такие методы должны быть во всех ключевых WinForms-объектах библиотеки.


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

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//+------------------------------------------------------------------+
bool CContainer::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)
  {
//--- Если тип объекта - меньше, чем базовый WinForms-объект
   if(element_type<GRAPH_ELEMENT_TYPE_WF_BASE)
     {
      //--- сообщаем об ошибке и возвращаем false
      CMessage::ToLog(DFUN,MSG_PANEL_OBJECT_ERR_OBJ_MUST_BE_WFBASE);
      return false;
     }
//--- Если не удалось создать новый графический элемент - возвращаем false
   CWinFormBase *obj=CForm::CreateAndAddNewElement(element_type,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Устанавливаем параметры созданному объекту
   this.SetObjParams(obj,colour);
//--- Если у панели включено автоизменение размеров и есть привязанные объекты - вызываем метод изменения размеров
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Обрезаем по краям видимой части
   obj.Crop();
//--- возвращаем true
   return true;
  }
//+------------------------------------------------------------------+

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

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

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

В конце метода обрежем скрытые области созданного объекта.

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

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

//+------------------------------------------------------------------+
//| Обрезает изображение, очерченное рассчитываемой                  |
//| прямоугольной областью видимости                                 |
//+------------------------------------------------------------------+
void CContainer::Crop(void)
  {
//--- Получаем указатель на базовый объект
   CContainer *base=this.GetBase();
//--- Если у объекта нет базового, к которому он прикреплён, то и обрезать скрытые области не нужно - уходим
   if(base==NULL)
      return;
//--- Задаём начальные координаты и размеры области видимости во весь объект
   int vis_x=0;
   int vis_y=0;
   int vis_w=this.Width();
   int vis_h=this.Height();
//--- Задаём размеры областей сверху, снизу, слева и справа, которые выходят за пределы контейнера
   int crop_top=0;
   int crop_bottom=0;
   int crop_left=0;
   int crop_right=0;
//--- Рассчитываем границы области контейнера, внутри которой объект полностью виден
   int top=fmax(base.CoordYWorkspace(),base.CoordYVisibleArea());
   int bottom=fmin(base.BottomEdgeWorkspace(),base.BottomEdgeVisibleArea()+1);
   int left=fmax(base.CoordXWorkspace(),base.CoordXVisibleArea());
   int right=fmin(base.RightEdgeWorkspace(),base.RightEdgeVisibleArea()+1);
//--- Рассчитываем величины областей сверху, снизу, слева и справа, на которые объект выходит
//--- за пределы границ области контейнера, внутри которой объект полностью виден
   crop_top=this.CoordY()-top;
   if(crop_top<0)
      vis_y=-crop_top;
   crop_bottom=bottom-this.BottomEdge()-1;
   if(crop_bottom<0)
      vis_h=this.Height()+crop_bottom-vis_y;
   crop_left=this.CoordX()-left;
   if(crop_left<0)
      vis_x=-crop_left;
   crop_right=right-this.RightEdge()-1;
   if(crop_right<0)
      vis_w=this.Width()+crop_right-vis_x;
//--- Если есть области, которые необходимо скрыть - вызываем метод обрезания с рассчитанными размерами области видимости объекта
   if(crop_top<0 || crop_bottom<0 || crop_left<0 || crop_right<0)
      this.Crop(vis_x,vis_y,vis_w,vis_h);
  }
//+------------------------------------------------------------------+

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

В методе ArrangeObjects() ранее переименованные методы, из которых убрана приставка "Get", уже заменены на текущие их наименования.

Для примера:

//+------------------------------------------------------------------+
//| Располагает привязанные объекты в порядке их Dock-привязки       |
//+------------------------------------------------------------------+
bool CContainer::ArrangeObjects(const bool redraw)
  {
//--- Получаем список прикреплённых объектов с типом WinForms базовый и выше
   CArrayObj *list=this.GetListWinFormsObj();
   CWinFormBase *prev=NULL, *obj=NULL, *elm=NULL;
//--- В цикле во всем привязанным объектам
   for(int i=0;i<list.Total();i++)
     {
      //--- Получаем текущий и предыдущий элементы из списка
      obj=list.At(i);
      prev=list.At(i-1);
      //--- Если объект не получен - идём далее
      if(obj==NULL)
         continue;
      int x=0, y=0; // Координаты привязки объекта
      //--- В зависимости от режима привязки текущего объекта ...
      //--- Привязка сверху
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_TOP)
        {
         //--- Если не удалось изменить размеры объекта (на всю ширину рабочей области и на изначальную высоту объекта) - идём к следующему
         if(!obj.Resize(this.WidthWorkspace(),obj.GetHeightInit(),false))
            continue;
         //--- Получаем координаты привязки объекта
         x=this.CoordXWorkspace();
         y=(prev!=NULL ? prev.BottomEdge()+1 : this.CoordYWorkspace());
         //--- Если не удалось сместить объект на полученные координаты - идём к следующему
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Привязка снизу

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


Доработаем класс объекта-панели в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh.

Подключим к нему файлы всех созданных сегодня новых классов:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "Container.mqh"
#include "..\TabField.mqh"
#include "..\ArrowButton.mqh"
#include "..\ArrowUpButton.mqh"
#include "..\ArrowDownButton.mqh"
#include "..\ArrowLeftButton.mqh"
#include "..\ArrowRightButton.mqh"
#include "..\ArrowUpDownBox.mqh"
#include "..\ArrowLeftRightBox.mqh"
#include "GroupBox.mqh"
#include "TabControl.mqh"
#include "..\..\WForms\Common Controls\ListBox.mqh"
#include "..\..\WForms\Common Controls\CheckedListBox.mqh"
#include "..\..\WForms\Common Controls\ButtonListBox.mqh"
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Создаёт новый графический объект                                 |
//+------------------------------------------------------------------+
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.ID(),obj_num,this.ChartID(),this.SubWindow(),descript,x,y,w,h,colour,opacity,movable,activity);
        break;
      case GRAPH_ELEMENT_TYPE_FORM :
         element=new CForm(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CONTAINER            :
         element=new CContainer(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_GROUPBOX             :
         element=new CGroupBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_PANEL                :
         element=new CPanel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LABEL                :
         element=new CLabel(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKBOX             :
         element=new CCheckBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_RADIOBUTTON          :
         element=new CRadioButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON               :
         element=new CButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX             :
         element=new CListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_LIST_BOX_ITEM        :
         element=new CListBoxItem(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_CHECKED_LIST_BOX     :
         element=new CCheckedListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_BUTTON_LIST_BOX      :
         element=new CButtonListBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_HEADER           :
         element=new CTabHeader(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_FIELD            :
         element=new CTabField(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL          :
         element=new CTabControl(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON         :
         element=new CArrowButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP      :
         element=new CArrowUpButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN    :
         element=new CArrowDownButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT    :
         element=new CArrowLeftButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT   :
         element=new CArrowRightButton(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX :
         element=new CArrowUpDownBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      case GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX :
         element=new CArrowLeftRightBox(this.ChartID(),this.SubWindow(),descript,x,y,w,h);
        break;
      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.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;
  }
//+------------------------------------------------------------------+


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

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


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

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

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


Доработаем класс объекта-заголовка вкладки элемента управления TabControl в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\TabHeader.mqh.

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

//--- Устанавливает состояние элемента управления
   virtual void      SetState(const bool flag);

//--- Перерисовывает объект
   virtual void      Redraw(bool redraw);
//--- Очищает элемент с заполнением его цветом и непрозрачностью


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

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const ENUM_GRAPH_ELEMENT_TYPE type,
                       const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);

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

   this.SetSizes(w,h);
   this.SetState(false);
  }
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CTabHeader::CTabHeader(const long chart_id,
                       const int subwindow,
                       const string descript,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CButton(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetToggleFlag(true);

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

   this.SetSizes(w,h);
   this.SetState(false);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Устанавливает состояние элемента управления                      |
//+------------------------------------------------------------------+
void CTabHeader::SetState(const bool flag)
  {
//--- Получаем состояние кнопки и устанавливаем новое, переданное в метод
   bool state=this.State();
   CButton::SetState(flag);
//--- Если прошлое состояние кнопки не соответствует установленному
   if(state!=this.State())
     {
      //--- Если кнопка нажата
      if(this.State())
        {
         //--- Вызывем метод изменения размера кнопки и выводим её на передний план
         this.WHProcessStateOn();
         if(this.IsVisible())
            this.BringToTop();
         //--- Получаем базовый объект, к которому прикреплён заголовок вкладки (TabControl)
         CWinFormBase *base=this.GetBase();
         if(base==NULL)
            return;
         //--- Устанавливаем в объект TabControl номер выбранной вкладки
         base.SetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER,this.PageNumber());
         //--- Получаем из базового объекта список объектов-полей вкладок
         CArrayObj *list=base.GetListElementsByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
         if(list==NULL)
            return;
         //--- В цикле по полученному списку скрываем все поля, не соответствующие заголовку
         for(int i=0;i<list.Total();i++)
           {
            //--- получаем очередной объект-поле вкладки
            CWinFormBase *obj=list.At(i);
            //--- Если объект не получен, либо соответствует выбранному заголовку - идём дальше
            if(obj==NULL || obj.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER)==this.PageNumber())
               continue;
            //--- Устанавливаем полю вкладки ZOrder как у базового объекта и скрываем поле
            obj.SetZorder(base.Zorder(),false);
            obj.Hide();
           }
         //--- Получаем объект-поле, соответствующий заголовку поля (этому объекту)
         CWinFormBase *field=this.GetFieldObj();
         if(field==NULL)
            return;
         //--- Отображаем поле и устанавливаем ему ZOrder выше, чем у остальных полей объекта TabControl,
         //--- рисуем рамку объекта-поля и переносим его на передний план

         field.SetZorder(base.Zorder()+1,false);
         if(this.IsVisible())
           {
            field.Show();
            field.DrawFrame();
            field.Crop();
            field.BringToTop();
           }
        }
      //--- Если кнопка не нажата - вызываем метод восстановления размера заголовка
      else
        {
         this.WHProcessStateOff();
         CWinFormBase *field=this.GetFieldObj();
         field.Hide();
        }
     }
  }
//+------------------------------------------------------------------+

Если кнопка не нажата — объект-поле вкладки будем скрывать.

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

//+------------------------------------------------------------------+
//| Очищает элемент с заполнением его цветом и непрозрачностью       |
//+------------------------------------------------------------------+
void CTabHeader::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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Очищает элемент заливкой градиентом                              |
//+------------------------------------------------------------------+
void CTabHeader::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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

Заливаем цветом, рисуем элементы оформления и обрезаем лишнее.


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

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CTabHeader::Redraw(bool redraw)
  {
//--- Заполняем объект цветом фона
   this.Erase(this.BackgroundColor(),this.Opacity(),false);
//--- Объявляем переменные для координат X и Y и устанавливаем их значения в зависимости от выравнивания текста
   int x=0,y=0;
   CLabel::SetTextParamsByAlign(x,y);
//--- Рисуем текст в установленных координатах объекта и точкой привязки текста и обновляем объект 
   this.Text(x,y,this.Text(),this.ForeColor(),this.ForeColorOpacity(),this.TextAnchor());
   this.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+

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

В обработчике события "Курсор в пределах активной области,  отжата кнопка мышки (левая)" (метод MouseActiveAreaReleasedHandler())
добавим обрезание поля вкладки
при отображении его на элементе после щелчка по заголовку вкладки:

      //--- Если это кнопка-переключатель -
      else
        {
         //--- если кнопка не работает в группе - устанавливаем её состояние на противоположное,
         if(!this.GroupButtonFlag())
            this.SetState(!this.State());
         //--- иначе - если кнопка ещё не нажата - устанавливаем её в нажатое состояние
         else if(!this.State())
            this.SetState(true);
         //--- устанавливаем цвет фона и текста для состояния "Курсор мышки над активной зоной" в зависимости от того нажата кнопка или нет
         this.SetBackgroundColor(this.State() ? this.BackgroundStateOnColorMouseOver() : this.BackgroundColorMouseOver(),false);
         this.SetForeColor(this.State() ? this.ForeStateOnColorMouseOver() : this.ForeColorMouseOver(),false);
         
         //--- Получаем объект-поле, соответствующий этому заголовку
         CWinFormBase *field=this.GetFieldObj();
         if(field!=NULL)
           {
            //--- Отображаем поле, выводим его на передний план и рисуем рамку
            field.Show();
            field.BringToTop();
            field.DrawFrame();
            field.Crop();
           }
         //--- Перерисовываем объект и график
         this.Redraw(true);
        }
      //--- Выводим тестовое сообщение в журнал
      Print(DFUN_ERR_LINE,TextByLanguage("Щелчок","Click"),", this.State()=",this.State(),", ID=",this.ID(),", Group=",this.Group());
      //--- Устанавливаем цвет рамки для состояния "Курсор мышки над активной зоной"
      this.SetBorderColor(this.BorderColorMouseOver(),false);
     }
  }
//+------------------------------------------------------------------+


Доработаем класс объекта-поля вкладки в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\TabField.mqh.

В конструкторах класса установим тип графического объекта библиотеки как "вспомогательный WinForms-объект":

//+------------------------------------------------------------------+
//| Защищённый конструктор с указанием типа объекта,                 |
//| идентификатора чарта и подокна                                   |
//+------------------------------------------------------------------+
CTabField::CTabField(const ENUM_GRAPH_ELEMENT_TYPE type,
                     const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CContainer(type,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
   
   //---...
   //---...

   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetPaddingAll(3);
  }
//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CTabField::CTabField(const long chart_id,
                     const int subwindow,
                     const string descript,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD);
   this.m_type=OBJECT_DE_TYPE_GWF_HELPER;
   this.SetBorderSizeAll(1);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);

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

   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetPaddingAll(3);
  }
//+------------------------------------------------------------------+


Методы очистки элемента доработаны точно так же, как и во всех предыдущих классах WinForms-объектов:

//+------------------------------------------------------------------+
//| Очищает элемент с заполнением его цветом и непрозрачностью       |
//+------------------------------------------------------------------+
void CTabField::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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+
//| Очищает элемент заливкой градиентом                              |
//+------------------------------------------------------------------+
void CTabField::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.Crop();
   this.Update(redraw);
  }
//+------------------------------------------------------------------+


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

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


Доработаем класс WinForms-объекта TabControl в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\TabControl.mqh.

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

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

//--- Возвращает (1) номер, (2) указатель на выбранную вкладку
   int               SelectedTabPageNum(void)      const { return (int)this.GetProperty(CANV_ELEMENT_PROP_TAB_PAGE_NUMBER);}
   CWinFormBase     *SelectedTabPage(void)               { return this.GetTabField(this.SelectedTabPageNum());             }
   
//--- Устанавливает объект выше всех
   virtual void      BringToTop(void);
//--- Показывает элемент управления
   virtual void      Show(void);
   
//--- Конструктор


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

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CTabControl::CTabControl(const long chart_id,
                         const int subwindow,
                         const string descript,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CContainer(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,chart_id,subwindow,descript,x,y,w,h)
  {
   this.SetTypeElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetBorderSizeAll(0);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetOpacity(0,true);
   this.SetBackgroundColor(CLR_CANV_NULL,true);
   this.SetBackgroundColorMouseDown(CLR_CANV_NULL);
   this.SetBackgroundColorMouseOver(CLR_CANV_NULL);
   this.SetBorderColor(CLR_CANV_NULL,true);
   this.SetBorderColorMouseDown(CLR_CANV_NULL);
   this.SetBorderColorMouseOver(CLR_CANV_NULL);
   this.SetForeColor(CLR_DEF_FORE_COLOR,true);
   this.SetAlignment(CANV_ELEMENT_ALIGNMENT_TOP);
   this.SetItemSize(58,18);
   this.SetTabSizeMode(CANV_ELEMENT_TAB_SIZE_MODE_NORMAL);
   this.SetPaddingAll(0);
   this.SetHeaderPadding(6,3);
   this.SetFieldPadding(3,3,3,3);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Создаёт указанное количество вкладок                             |
//+------------------------------------------------------------------+
bool CTabControl::CreateTabPages(const int total,const int selected_page,const int tab_w=0,const int tab_h=0,const string header_text="")
  {
//--- Рассчитываем размеры и начальные координаты заголовка вкладки
   int w=(tab_w==0 ? this.ItemWidth()  : tab_w);
   int h=(tab_h==0 ? this.ItemHeight() : tab_h);

//--- В цикле по количеству вкладок
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
   for(int i=0;i<total;i++)
     {
      //--- В зависимости от расположения заголовков вкладок устанавливаем их начальные координаты
      int header_x=2;
      int header_y=2;
      int header_w=w;
      int header_h=h;
      
      //--- Устанавливаем текущую координату X или Y в зависимости от расположения заголовков вкладок
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           header_w=w;
           header_h=h;
           header_x=(header==NULL ? 2 : header.RightEdgeRelative());
           header_y=this.Height()-header_h;
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           header_w=h;
           header_h=w;
           header_x=2;
           header_y=(header==NULL ? this.Height()-header_h-2 : header.CoordYRelative()-header_h);
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           header_w=h;
           header_h=w;
           header_x=this.Width()-header_w-2;
           header_y=(header==NULL ? 2 : header.BottomEdgeRelative());
           break;
         default:
           break;
        }
      //--- Создаём объект TabHeader
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,header_x,header_y,header_w,header_h,clrNONE,255,this.Active(),false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER,i);
      if(header==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_HEADER),string(i+1));
         return false;
        }
      header.SetBase(this.GetObject());
      header.SetPageNumber(i);
      header.SetGroup(this.Group()+1);
      header.SetBackgroundColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR,true);
      header.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_DOWN);
      header.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_MOUSE_OVER);
      header.SetBackgroundStateOnColor(CLR_DEF_CONTROL_TAB_HEAD_BACK_COLOR_ON,true);
      header.SetBackgroundStateOnColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BACK_DOWN_ON);
      header.SetBackgroundStateOnColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BACK_OVER_ON);
      header.SetBorderStyle(FRAME_STYLE_SIMPLE);
      header.SetBorderColor(CLR_DEF_CONTROL_TAB_HEAD_BORDER_COLOR,true);
      header.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_DOWN);
      header.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_HEAD_BORDER_MOUSE_OVER);
      header.SetAlignment(this.Alignment());
      header.SetPadding(this.HeaderPaddingWidth(),this.HeaderPaddingHeight(),this.HeaderPaddingWidth(),this.HeaderPaddingHeight());
      if(header_text!="" && header_text!=NULL)
         this.SetHeaderText(header,header_text+string(i+1));
      else
         this.SetHeaderText(header,"TabPage"+string(i+1));
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT)
         header.SetFontAngle(90);
      if(this.Alignment()==CANV_ELEMENT_ALIGNMENT_RIGHT)
         header.SetFontAngle(270);
      header.SetTabSizeMode(this.TabSizeMode());
      
      //--- Сохраняем изначальную высоту заголовка и устанавливаем его размеры в соответствии с режимом установки размеров заголовков
      int h_prev=header_h;
      header.SetSizes(header_w,header_h);
      //--- Получаем смещение по Y расположения заголовка после изменения его высоты и
      //--- сдвигаем его на рассчитанную величину только для расположения заголовков слева
      int y_shift=header.Height()-h_prev;
      if(header.Move(header.CoordX(),header.CoordY()-(this.Alignment()==CANV_ELEMENT_ALIGNMENT_LEFT ? y_shift : 0)))
        {
         header.SetCoordXRelative(header.CoordX()-this.CoordX());
         header.SetCoordYRelative(header.CoordY()-this.CoordY());
        }
      header.SetVisibleFlag(this.IsVisible(),false);

      //--- В зависимости от расположения заголовков вкладок устанавливаем начальные координаты полей вкладок
      int field_x=0;
      int field_y=0;
      int field_w=this.Width();
      int field_h=this.Height()-header.Height()-2;
      int header_shift=0;
      
      switch(this.Alignment())
        {
         case CANV_ELEMENT_ALIGNMENT_TOP     :
           field_x=0;
           field_y=header.BottomEdgeRelative();
           field_w=this.Width();
           field_h=this.Height()-header.Height()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_BOTTOM  :
           field_x=0;
           field_y=0;
           field_w=this.Width();
           field_h=this.Height()-header.Height();
           break;
         case CANV_ELEMENT_ALIGNMENT_LEFT    :
           field_x=header.RightEdgeRelative();
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         case CANV_ELEMENT_ALIGNMENT_RIGHT   :
           field_x=0;
           field_y=0;
           field_h=this.Height();
           field_w=this.Width()-header.Width()-2;
           break;
         default:
           break;
        }
      
      //--- Создаём объект TabField (поле вкладки)
      if(!this.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,field_x,field_y,field_w,field_h,clrNONE,255,true,false))
        {
         ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_FAILED_CREATE_ELM_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field=this.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD,i);
      if(field==NULL)
        {
         ::Print(DFUN,CMessage::Text(MSG_ELM_LIST_ERR_FAILED_GET_GRAPH_ELEMENT_OBJ),this.TypeElementDescription(GRAPH_ELEMENT_TYPE_WF_TAB_FIELD),string(i+1));
         return false;
        }
      field.SetBase(this.GetObject());
      field.SetPageNumber(i);
      field.SetGroup(this.Group()+1);
      field.SetBorderSizeAll(1);
      field.SetBorderStyle(FRAME_STYLE_SIMPLE);
      field.SetOpacity(CLR_DEF_CONTROL_TAB_PAGE_OPACITY,true);
      field.SetBackgroundColor(CLR_DEF_CONTROL_TAB_PAGE_BACK_COLOR,true);
      field.SetBackgroundColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_DOWN);
      field.SetBackgroundColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_MOUSE_OVER);
      field.SetBorderColor(CLR_DEF_CONTROL_TAB_PAGE_BORDER_COLOR,true);
      field.SetBorderColorMouseDown(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_DOWN);
      field.SetBorderColorMouseOver(CLR_DEF_CONTROL_TAB_PAGE_BORDER_MOUSE_OVER);
      field.SetForeColor(CLR_DEF_FORE_COLOR,true);
      field.SetPadding(this.FieldPaddingLeft(),this.FieldPaddingTop(),this.FieldPaddingRight(),this.FieldPaddingBottom());
      field.Hide();
     }
//--- Выстраиваем все заголовки в соответствии с установленными режимами их отображения и выбираем указанную вкладку
   this.ArrangeTabHeaders();
   this.Select(selected_page,true);
   return true;
  }
//+------------------------------------------------------------------+

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


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

      //--- Получаем ширину контейнера, количество заголовков в ряду и рассчитываем ширину каждого заголовка
      int base_size=this.Width()-4;
      int num=list_row.Total();
      int w=(int)round((double)base_size/double(num>0 ? num : 1));
      //--- В цикле по заголовкам ряда
      for(int j=0;j<list_row.Total();j++)
        {


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

//+------------------------------------------------------------------+
//| Устанавливает вкладку выбранной                                  |
//+------------------------------------------------------------------+
void CTabControl::SetSelected(const int index)
  {
//--- Получаем заголовок по индексу и
   CTabHeader *header=this.GetTabHeader(index);
   if(header==NULL)
      return;
//--- устанавливаем его в состояние "выбран"
   if(!header.State())
     {
      CArrayObj *list=this.GetListHeaders();
      if(list==NULL)
         return;
      for(int i=0;i<list.Total();i++)
        {
         if(i==index)
            continue;
         this.SetUnselected(i);
        }
      header.SetState(true);
     }
//--- сохраним номер выбранной вкладки
   this.SetSelectedTabPageNum(index);
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Устанавливает текст заголовка указанной вкладки                  |
//+------------------------------------------------------------------+
void CTabControl::SetHeaderText(CTabHeader *header,const string text)
  {
   if(header==NULL)
      return;
   header.SetText(text);
   header.Crop();
  }
//+------------------------------------------------------------------+

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


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

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


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

//+------------------------------------------------------------------+
//| Показывает элемент управления                                    |
//+------------------------------------------------------------------+
void CTabControl::Show(void)
  {
//--- Получаем список всех заголовков вкладок
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- Если у объекта есть тень - отобразим её
   if(this.m_shadow_obj!=NULL)
      this.m_shadow_obj.Show();
//--- Отобразим контейнер
   CGCnvElement::Show();
//--- Перенесём все элементы объекта на передний план
   this.BringToTop();
  }
//+------------------------------------------------------------------+

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


Метод, устанавливающий объект выше всех (на передний план):

//+------------------------------------------------------------------+
//| Устанавливает объект выше всех                                   |
//+------------------------------------------------------------------+
void CTabControl::BringToTop(void)
  {
//--- Переносим все элементы объекта на передний план
   CForm::BringToTop();
//--- Получим номер выбранной вкладки
   int selected=this.SelectedTabPageNum();
//--- Объявим указатели на объекты-заголовки вкладок и поля вкладок
   CTabHeader *header=NULL;
   CTabField  *field=NULL;
//--- Получим список всех заголовков вкладок
   CArrayObj *list=this.GetListHeaders();
   if(list==NULL)
      return;
//--- В цикле по списку заголовков вкладок
   for(int i=0;i<list.Total();i++)
     {
      //--- получаем очередной заголовок и, если получить объект не удалось,
      //--- или это заголовок выбранной вкладки - пропускаем его
      header=list.At(i);
      if(header==NULL || header.PageNumber()==selected)
         continue;
      //--- выводим заголовок на передний план
      header.BringToTop();
      //--- получаем поле вкладки, соответствующее текущему заголовку
      field=header.GetFieldObj();
      if(field==NULL)
         continue;
      //--- Скрываем поле вкладки
      field.Hide();
     }
//--- Получаем указатель на заголовок выбранной вкладки
   header=this.GetTabHeader(selected);
   if(header!=NULL)
     {
      //--- вывоим заголовок на передний план
      header.BringToTop();
      //--- получаем поле вкладки, соответствующее заголовку выбранной вкладки
      field=header.GetFieldObj();
      //--- Выводим поле вкладки на передний план
      if(field!=NULL)
         field.BringToTop();
     }
  }
//+------------------------------------------------------------------+

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

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


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

//+------------------------------------------------------------------+
//| Возвращает указатель на форму, находящуюся под курсором          |
//+------------------------------------------------------------------+
CForm *CGraphElementsCollection::GetFormUnderCursor(const int id, 
                                                    const long &lparam, 
                                                    const double &dparam, 
                                                    const string &sparam,
                                                    ENUM_MOUSE_FORM_STATE &mouse_state,
                                                    long &obj_ext_id,
                                                    int &form_index)
  {
//--- Устанавливаем идентификатор расширенного стандартного графического объекта в -1 
//--- и индекс точки привязки, которой управляет форма, в -1
   obj_ext_id=WRONG_VALUE;
   form_index=WRONG_VALUE;
//--- Инициализируем состояние мышки относительно формы
   mouse_state=MOUSE_FORM_STATE_NONE;
//--- Объявим указатели на объекты класса-коллекции графических элементов
   CGCnvElement *elm=NULL;
   CForm *form=NULL;
//--- Получим список объектов, для которых установлен флаг взаимодействия (должен быть всего один объект)
   CArrayObj *list=CSelect::ByGraphCanvElementProperty(GetListCanvElm(),CANV_ELEMENT_PROP_INTERACTION,true,EQUAL);
//--- Если список получить удалось и он не пустой
   if(list!=NULL && list.Total()>0)
     {
      //--- Получаем единственный в нём графический элемент
      elm=list.At(0);
      //--- Если этот элемент - объект-форма, или её наследники
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE && elm.IsVisible())
        {
         //--- Присваиваем указатель на элемент указателю на объект-форму
         form=elm;
         //--- Получаем состояние мышки относительно формы
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- Если курсор в пределах формы
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
           {
            //--- Найдём объект взаимодействия.
            //--- Это будет либо найденный объект, либо та же форма
            form=this.SearchInteractObj(form,id,lparam,dparam,sparam);
            //--- Возвращаем объект-форму
            //Comment(form.TypeElementDescription()," ",form.Name());
            return form;
           }
        }
     }
//--- Если нет ни одного объекта-формы с установленным флагом взаимодействия -
//--- в цикле по всем объектам класса-коллекции графических элементов
   int total=this.m_list_all_canv_elm_obj.Total();
   for(int i=0;i<total;i++)
     {
      //--- получаем очередной элемент
      elm=this.m_list_all_canv_elm_obj.At(i);
      if(elm==NULL || !elm.IsVisible() || !elm.Enabled())
         continue;
      //--- если полученный элемент - объект-форма, или её наследники
      if(elm.TypeGraphElement()>=GRAPH_ELEMENT_TYPE_WF_BASE)
        {
         //--- Присваиваем указатель на элемент указателю на объект-форму
         form=elm;
         //--- Получаем состояние мышки относительно формы
         mouse_state=form.MouseFormState(id,lparam,dparam,sparam);
         //--- Если курсор в пределах формы - возвращаем указатель на форму
         if(mouse_state>MOUSE_FORM_STATE_OUTSIDE_FORM_WHEEL)
           {
            //--- Найдём объект взаимодействия.
            //--- Это будет либо найденный объект, либо та же форма
            form=this.SearchInteractObj(form,id,lparam,dparam,sparam);
            //--- Возвращаем объект-форму
            //Comment(form.TypeElementDescription()," ",form.Name());
            return form;
           }
        }
     }
//--- ...
//--- ...

//---...
//--- Ничего не нашли - возвращаем NULL
   return NULL;
  }
//+------------------------------------------------------------------+


В методе постобработки бывшей активной формы под курсором тоже добавим проверку флага видимости и обработку объекта TabControl:

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

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

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

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

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

Что будем тестировать? Создадим на основной панели один элемент управления TabControl. На его первой вкладке разместим все объекты созданных сегодня классов объектов-кнопок со стрелками. Горизонтальные двойные стрелки влево-вправо будут иметь ширину меньше установленной по умолчанию — чтобы поглядеть возможность изменения их размера. Для координат расположения элемента управления TabControl добавим две входные переменные. Тогда мы сможем задать значения начальных координат так, чтобы создаваемый объект мог выходить за пределы своего контейнера, чтобы протестировать методы обрезания изображения, выходящего из области видимости. Впрочем, это и так будет видно по заголовкам вкладок, которых будет 11 штук, и они не будут умещаться по размерам элемента управления при размещении их в один ряд.

В блоке входных переменных объявим две новые переменные:

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


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

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- Установка глобальных переменных советника
   ArrayResize(array_clr,2);        // Массив цветов градиентной заливки
   array_clr[0]=C'26,100,128';      // Исходный ≈Тёмно-лазурный цвет
   array_clr[1]=C'35,133,169';      // Осветлённый исходный цвет
//--- Создадим массив с текущим символом и установим его для использования в библиотеке
   string array[1]={Symbol()};
   engine.SetUsedSymbols(array);
   //--- Создадим объект-таймсерию для текущего символа и периода и выведем его описание в журнал
   engine.SeriesCreate(Symbol(),Period());
   engine.GetTimeSeriesCollection().PrintShort(false); // Краткие описания

//--- Создадим объект WinForms Panel
   CPanel *pnl=NULL;
   pnl=engine.CreateWFPanel("WFPanel",50,50,410,200,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
   if(pnl!=NULL)
     {
      pnl.Hide();
      Print(DFUN,"Видимость панели: ",pnl.IsVisible(),": ",pnl.TypeElementDescription()," ",pnl.Name());
      //--- Установим значение Padding равным 4
      pnl.SetPaddingAll(3);
      //--- Установим флаги перемещаемости, автоизменения размеров и режим автоизменения из входных параметров
      pnl.SetMovable(InpMovable);
      pnl.SetAutoSize(InpAutoSize,false);
      pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);

      pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,InpTabControlX,InpTabControlY,pnl.Width()-30,pnl.Height()-40,clrNONE,255,true,false);
      CTabControl *tc=pnl.GetElementByType(GRAPH_ELEMENT_TYPE_WF_TAB_CONTROL,0);
      if(tc!=NULL)
        {
         tc.SetTabSizeMode((ENUM_CANV_ELEMENT_TAB_SIZE_MODE)InpTabPageSizeMode);
         tc.SetAlignment((ENUM_CANV_ELEMENT_ALIGNMENT)InpHeaderAlignment);
         tc.SetMultiline(InpTabCtrlMultiline);
         tc.SetHeaderPadding(6,0);
         tc.CreateTabPages(11,0,56,16,TextByLanguage("Вкладка","TabPage"));
         //---
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_LEFT,10,10,15,15,clrNONE,255,true,false);
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_UP,30,10,15,15,clrNONE,255,true,false);
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_DOWN,50,10,15,15,clrNONE,255,true,false);
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTON_RIGHT,70,10,15,15,clrNONE,255,true,false);
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_UD_BOX,10,30,13,13,clrNONE,255,true,false);
         tc.CreateNewElement(0,GRAPH_ELEMENT_TYPE_WF_ARROW_BUTTONS_LR_BOX,40,30,9,13,clrNONE,255,true,false);
        }
        
      //--- Перерисуем все объекты в порядке их иерархии
      pnl.Show();
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+

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

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


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


Что дальше

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

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

К содержанию

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

DoEasy. Элементы управления (Часть 10): WinForms-объекты — оживляем интерфейс
DoEasy. Элементы управления (Часть 11): WinForms-объекты — группы, WinForms-объект CheckedListBox
DoEasy. Элементы управления (Часть 12): Базовый объект-список, WinForms-объекты ListBox и ButtonListBox
DoEasy. Элементы управления (Часть 13): Оптимизация взаимодействия WinForms-объектов с мышкой, начало разработки WinForms-объекта TabControl
DoEasy. Элементы управления (Часть 14): Новый алгоритм именования графических элементов. Продолжаем работу над WinForms-объектом TabControl
DoEasy. Элементы управления (Часть 15): WinForms-объект TabControl — несколько рядов заголовков вкладок, методы работы с вкладками
DoEasy. Элементы управления (Часть 16): WinForms-объект TabControl — несколько рядов заголовков вкладок, режим растягивания заголовков под размеры контейнера

Прикрепленные файлы |
MQL5.zip (4534.73 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (8)
Artyom Trishkin
Artyom Trishkin | 10 сент. 2022 в 16:55
Aliaksandr Hryshyn #:

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

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

Внёс небольшие правки в исходный код текущей статьи в советник:

Немного поигрался с панельками, результат такой:

Переключение окна на передний план происходит при отжатии мышки, хотя должно при нажатии.

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

Прослушка событий с другого графика реализована через индикатор-шпион.
Если у Вас есть иной лучший способ, подскажите, рассмотрим вариант.
События не работают в тестере и так.
Спасибо за указание на недоработку - исправим, это же всё пока ещё на стадии разработки. Того самого костяка, о котором Вы говорите.
Aliaksandr Hryshyn
Aliaksandr Hryshyn | 10 сент. 2022 в 22:04

А для чего нужны события с других графиков?

Покликал своё визуальное приложение, параллельно запущен ваш советник, в логи пишется такое:

2022.09.10 22:18:36.256 TestDoEasy117 (AUDUSD,M1)       Графическая метка "1716653226_u_0": ID 10001, 2022.09.10 22:16:34
2022.09.10 22:18:36.256 TestDoEasy117 (AUDUSD,M1)       OnChartEvent: Состояние кнопки: Отжата
2022.09.10 22:18:36.256 TestDoEasy117 (AUDUSD,M1)       OnChartEvent: Изменено свойство графического объекта:
2022.09.10 22:18:36.256 TestDoEasy117 (AUDUSD,M1)       Графическая метка "1716653226_u_0": ID 10001, 2022.09.10 22:16:34
2022.09.10 22:18:36.256 TestDoEasy117 (AUDUSD,M1)       OnChartEvent: Имя BMP-файла:
2022.09.10 22:18:36.256 TestDoEasy117 (AUDUSD,M1)        - Состояние "On" [0]: "\Experts\Greshnik\Strategy_viewer.ex5::1716653226_u_012896816886410156413404032"
2022.09.10 22:18:36.256 TestDoEasy117 (AUDUSD,M1)        - Состояние "Off" [1]: Отсутствует
2022.09.10 22:18:38.291 TestDoEasy117 (AUDUSD,M1)       CGStdGraphObj::PropertiesCheckChanged: Создан снимок истории изменений графического объекта #19: 2022.09.09 23:54:59.875
2022.09.10 22:18:38.291 TestDoEasy117 (AUDUSD,M1)       OnChartEvent: Изменено свойство графического объекта:
2022.09.10 22:18:38.291 TestDoEasy117 (AUDUSD,M1)       Графическая метка "1716653226_u_0": ID 10001, 2022.09.10 22:16:34
2022.09.10 22:18:38.291 TestDoEasy117 (AUDUSD,M1)       OnChartEvent: Время создания: 2022.09.10 22:18:38
2022.09.10 22:18:38.291 TestDoEasy117 (AUDUSD,M1)       OnChartEvent: Изменено свойство графического объекта:
2022.09.10 22:18:38.291 TestDoEasy117 (AUDUSD,M1)       Графическая метка "1716653226_u_0": ID 10001, 2022.09.10 22:16:34
2022.09.10 22:18:38.291 TestDoEasy117 (AUDUSD,M1)       OnChartEvent: Имя BMP-файла:
2022.09.10 22:18:38.291 TestDoEasy117 (AUDUSD,M1)        - Состояние "On" [0]: "\Experts\Greshnik\Strategy_viewer.ex5::1716653226_u_012896816886410156413395097"
2022.09.10 22:18:38.291 TestDoEasy117 (AUDUSD,M1)        - Состояние "Off" [1]: Отсутствует
2022.09.10 22:18:38.959 TestDoEasy117 (AUDUSD,M1)       CGStdGraphObj::PropertiesCheckChanged: Создан снимок истории изменений графического объекта #20: 2022.09.09 23:54:59.875

Не понятно назначение этого.


Это очень медленно считается. Что будет с таблицей при большом количестве ячеек?

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

//--- Обработки событий коллекции графических элементов
   ulong mcs=GetMicrosecondCount();
   engine.GetGraphicObjCollection().OnChartEvent(id,lparam,dparam,sparam);
   Print((GetMicrosecondCount()-mcs)/1000.0);

Клик левой мышки по центру панели: 5 мс

Клик по заголовку активной вкладки: 7 мс

Клик по заголовку неактивной вкладки: 20 мс


Artyom Trishkin
Artyom Trishkin | 11 сент. 2022 в 22:30
Aliaksandr Hryshyn #:

А для чего нужны события с других графиков?

Покликал своё визуальное приложение, параллельно запущен ваш советник, в логи пишется такое:

Не понятно назначение этого.


Это очень медленно считается. Что будет с таблицей при большом количестве ячеек?

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

Клик левой мышки по центру панели: 5 мс

Клик по заголовку активной вкладки: 7 мс

Клик по заголовку неактивной вкладки: 20 мс


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

Anatoli Kazharski
Anatoli Kazharski | 12 сент. 2022 в 16:49
Artyom Trishkin #:
Спасибо, Анатолий, за отзыв. Конечно же, всё будет доведено до ума.
Хотелось бы знать, что бы тебе здесь хотелось бы видеть, какой функционал?
Очень не хватает обратной связи. 

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

А так, эту тему можно развивать бесконечно. Есть столько различных вариантов, к чему это всё может привести, что просто фантастика, какая-то. )

Мы ещё вернёмся к этому вопросу. Пока очень сильно занят. Продолжай работу, это очень интересно! 👍

Artyom Trishkin
Artyom Trishkin | 12 сент. 2022 в 17:59
Anatoli Kazharski #:

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

А так, эту тему можно развивать бесконечно. Есть столько различных вариантов, к чему это всё может привести, что просто фантастика, какая-то. )

Мы ещё вернёмся к этому вопросу. Пока очень сильно занят. Продолжай работу, это очень интересно! 👍

Добро 👌

Возможности Мастера MQL5, которые вам нужно знать (Часть 02): Карты Кохонена Возможности Мастера MQL5, которые вам нужно знать (Часть 02): Карты Кохонена
Благодаря Мастеру, трейдер экономит время при реализации своих идей. Кроме того, снижается вероятность ошибок, возникающих при дублировании кода. Вместо того чтобы тратить время на оформление кода, трейдеры претворяют в жизнь свою торговую философию.
Нейросети — это просто (Часть 28): Policy gradient алгоритм Нейросети — это просто (Часть 28): Policy gradient алгоритм
Продолжаем изучение методов обучение с подкреплением. В предыдущей статье мы познакомились с методом глубокого Q-обучения. В котором мы обучаем модель прогнозирования предстоящей награды в зависимости от совершаемого действия в конкретной ситуации. И далее совершаем действие в соответствии с нашей политикой и ожидаемой наградой. Но не всегда возможно аппроксимировать Q-функцию. Или её аппроксимация не даёт желаемого результата. В таких случаях используют методы аппроксимации не функции полезности, а на прямую политику (стратегию) действий. Именно к таким методам относится policy gradient.
Нейросети — это просто (Часть 29): Алгоритм актор-критик с преимуществом (Advantage actor-critic) Нейросети — это просто (Часть 29): Алгоритм актор-критик с преимуществом (Advantage actor-critic)
В предыдущих статьях данной серии мы познакомились с 2-мя алгоритмами обучения с подкреплением. Каждый из них обладает своими достоинствами и недостатками. Как часто бывает в таких случаях, появляется идея совместить оба метода в некий алгоритм, который бы вобрал в себя лучшее из двух. И тем самым компенсировать недостатки каждого из них. О таком методе мы и поговорим в этой статье.
Разработка торговой системы на основе индикатора Force Index Разработка торговой системы на основе индикатора Force Index
Это новая статья из серии, в которой мы учимся создавать торговые системы на основе популярных технических индикаторов. На этот раз будем изучать индикатор Индекса силы (Force Index) и будем учиться создавать на его основе торговые системы.