DoEasy. Элементы управления (Часть 8): Базовые WinForms-объекты по категориям, элементы управления "GroupBox" и "CheckBox"

10 июня 2022, 16:07
Artyom Trishkin
0
509

Содержание


Концепция

Создаваемые нами WinForms-объекты распределены по категориям наподобие MS Visual Studio:


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

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

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

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

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


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

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

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

//--- Параметры канваса
#define PAUSE_FOR_CANV_UPDATE          (16)                       // Частота обновления канваса
#define CLR_CANV_NULL                  (0x00FFFFFF)               // Ноль для канваса с альфа-каналом
#define CLR_DEF_FORE_COLOR             (C'0x2D,0x43,0x48')        // Цвет по умолчанию для текстов объектов на канвасе
#define CLR_DEF_FORE_COLOR_OPACITY     (255)                      // Непрозрачность цвета по умолчанию для текстов объектов на канвасе
#define CLR_DEF_FRAME_COLOR            (C'0x66,0x6C,0x6F')        // Цвет по умолчанию для рамок объектов на канвасе
#define CLR_DEF_FRAME_COLOR_OPACITY    (255)                      // Непрозрачность цвета по умолчанию для рамок объектов на канвасе
#define CLR_DEF_FRAME_COLOR_DARKNESS   (-2.0)                     // Затемнённость цвета по умолчанию для рамок объектов на канвасе (при использовании цвета фона)
#define CLR_DEF_FRAME_GBOX_COLOR       (C'0xDC,0xDC,0xDC')        // Цвет по умолчанию для рамок объектов GroupBox на канвасе
#define CLR_DEF_OPACITY                (200)                      // Непрозрачность цвета по умолчанию для объектов на канвасе
#define CLR_DEF_SHADOW_COLOR           (C'0x6B,0x6B,0x6B')        // Цвет по умолчанию для теней объектов на канвасе
#define CLR_DEF_SHADOW_OPACITY         (127)                      // Непрозрачность цвета по умолчанию для теней объектов на канвасе
#define DEF_SHADOW_BLUR                (4)                        // Размытие по умолчанию для теней объектов на канвасе
#define DEF_FONT                       ("Calibri")                // Шрифт по умолчанию
#define DEF_FONT_SIZE                  (8)                        // Размер шрифта по умолчанию
#define DEF_CHECK_SIZE                 (12)                       // Размер флажка проверки по умолчанию
#define OUTER_AREA_SIZE                (16)                       // Размер одной стороны внешней области вокруг рабочего пространства формы
#define DEF_FRAME_WIDTH_SIZE           (3)                        // Ширина рамки формы/панели/окна по умолчанию
//--- Параметры графических объектов

Флажок, в котором будет размещена галочка, в объекте CheckBox будет иметь размер по умолчанию 12x12 пикселей.

На данный момент у нас в списке типов объектов библиотеки в разделе WinForms прописаны типы Base, Panel и Label. Но это излишне — эти же типы у нас прописаны в другом перечислении. Но тут мы сможем их использовать как указание категории 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 станартный элемент управления"
//--- Анимация

Теперь для WinForms-объектов мы сможем тип объекта библиотеки использовать как тип и как категорию 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_COMMON_BASE,                 // Windows Forms базовый стандартный элемент управления
   GRAPH_ELEMENT_TYPE_WF_LABEL,                       // Windows Forms Label
   GRAPH_ELEMENT_TYPE_WF_CHECKBOX,                    // Windows Forms ChackBox
  };
//+------------------------------------------------------------------+


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

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


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

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

   CANV_ELEMENT_PROP_PADDING_LEFT,                    // Промежуток слева внутри элемента управления
   CANV_ELEMENT_PROP_PADDING_RIGHT,                   // Промежуток справа внутри элемента управления
   CANV_ELEMENT_PROP_TEXT_ALIGN,                      // Положение текста в границах текстовой метки
   CANV_ELEMENT_PROP_CHECK_ALIGN,                     // Положение флажка проверки в границах элемента управления
   CANV_ELEMENT_PROP_CHECKED,                         // Состояние флажка проверки элемента управления
   CANV_ELEMENT_PROP_CHECK_STATE,                     // Состояние элемента управления, имеющего флажок проверки
   CANV_ELEMENT_PROP_AUTOCHECK,                       // Автоматическое изменение состояния флажка при его выборе
   
  };
#define CANV_ELEMENT_PROP_INTEGER_TOTAL (48)          // Общее количество целочисленных свойств
#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_PADDING_LEFT,                 // Сортировать по промежутку слева внутри элемента управления
   SORT_BY_CANV_ELEMENT_PADDING_RIGHT,                // Сортировать по промежутку справа внутри элемента управления
   SORT_BY_CANV_ELEMENT_TEXT_ALIGN,                   // Сортировать по положению текста в границах текстовой метки
   SORT_BY_CANV_ELEMENT_CHECK_ALIGN,                  // Сортировать по положению флажка проверки в границах элемента управления
   SORT_BY_CANV_ELEMENT_CHECKED,                      // Сортировать по состоянию флажка проверки элемента управления
   SORT_BY_CANV_ELEMENT_CHECK_STATE,                  // Сортировать по состоянию элемента управления, имеющего флажок проверки
   SORT_BY_CANV_ELEMENT_AUTOCHECK,                    // Сортировать по автоматическому изменению состояния флажка при его выборе
//--- Сортировка по вещественным свойствам

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

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


Свойство "Тип рамки" (BorderStyle) для разных WinForms-объектов имеет разное назначение. Для объекта-панели это свойство указывает на стиль рамки, обрамляющей сам объект, а для создаваемого сегодня объекта GroupBox — это тип рамки, рисуемой вокруг группы объектов (сам объект рамки не имеет). Соответственно, мы можем использовать один и тот же метод в разных объектах для разного назначения.
Для этого сделаем его виртуальным в файле базового класса всех WinForms-объектов \MQL5\Include\DoEasy\Objects\Graph\WForms\WinFormBase.mqh:

//--- (1) Устанавливает, (2) возвращает тип толщины шрифта
   void              SetFontBoldType(ENUM_FW_TYPE type);
   ENUM_FW_TYPE      FontBoldType(void)                        const { return (ENUM_FW_TYPE)this.GetProperty(CANV_ELEMENT_PROP_BOLD_TYPE);               }
//--- (1) Устанавливает, (2) возвращает стиль рамки
   virtual void      SetBorderStyle(const ENUM_FRAME_STYLE style)    { this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,style);                           }
   ENUM_FRAME_STYLE  BorderStyle(void)                         const { return (ENUM_FRAME_STYLE)this.GetProperty(CANV_ELEMENT_PROP_BORDER_STYLE);        }

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


Базовый объект в категории "Контейнеры"

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

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

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


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

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

//+------------------------------------------------------------------+
//|                                                    Container.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 "..\..\WForms\WinFormBase.mqh"
#include "..\..\WForms\Common Controls\CheckBox.mqh"
//+------------------------------------------------------------------+
//| Класс базового объекта-контейнера элементов управления WForms    |
//+------------------------------------------------------------------+
class CContainer : public CWinFormBase
  {
private:
//--- Создаёт новый графический объект
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);

//--- Рассчитывает координаты привязки Dock-объектов
   void              CalculateCoords(CArrayObj *list);

protected:
//--- Подстраивает размеры элемента под его внутреннее содержимое
   bool              AutoSizeProcess(const bool redraw);
   
public:
//--- Возвращает размеры и координаты рабочей области
   int               GetWidthWorkspace(void)       const
                       {
                        return this.Width()-::fmax(this.FrameWidthLeft(),this.PaddingLeft())-::fmax(this.FrameWidthRight(),this.PaddingRight());
                       }
   int               GetHeightWorkspace(void)      const
                       {
                        return this.Height()-::fmax(this.FrameWidthTop(),this.PaddingTop())-::fmax(this.FrameWidthBottom(),this.PaddingBottom());
                       }
   int               GetCoordXWorkspace(void)      const
                       {
                        return this.CoordX()+::fmax(this.FrameWidthLeft(),this.PaddingLeft());
                       }
   int               GetCoordYWorkspace(void)      const
                       {
                        return this.CoordY()+::fmax(this.FrameWidthTop(),this.PaddingTop());
                       }
   int               GetRightEdgeWorkspace(void)   const
                       {
                        return this.RightEdge()-::fmax(this.FrameWidthRight(),this.PaddingRight());
                       }
   int               GetBottomEdgeWorkspace(void)  const
                       {
                        return this.BottomEdge()-::fmax(this.FrameWidthBottom(),this.PaddingBottom());
                       }

//--- Возвращает список прикреплённых WinForms-объектов с (1) любым, (2) указанным типом WinForms-объекта от базового и выше
   CArrayObj        *GetListWinFormsObj(void);
   CArrayObj        *GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type);
//--- Возвращает указатель на прикреплённый WinForms-объект с указанным типом по индексу
   CWinFormBase     *GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index);
   
//--- Устанавливает координату (1) X, (2) Y, (3) ширину, (4) высоту элемента
   virtual bool      SetCoordX(const int coord_x)              { return CGCnvElement::SetCoordX(coord_x);   }
   virtual bool      SetCoordY(const int coord_y)              { return CGCnvElement::SetCoordY(coord_y);   }
   virtual bool      SetWidth(const int width)                 { return CGCnvElement::SetWidth(width);      }
   virtual bool      SetHeight(const int height)               { return CGCnvElement::SetHeight(height);    }
   
//--- Создаёт новый присоединённый элемент
   virtual bool      CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                      CGCnvElement *main,
                                      const int x,
                                      const int y,
                                      const int w,
                                      const int h,
                                      const color colour,
                                      const uchar opacity,
                                      const bool activity,
                                      const bool redraw);
//--- Перерисовывает объект
   virtual void      Redraw(bool redraw)                             { CWinFormBase::Redraw(redraw);        }
   
//--- Сбрасывает размеры всех привязанных объектов к изначальным
   bool              ResetSizeAllToInit(void);
//--- Располагает привязанные объекты в порядке их Dock-привязки
   virtual bool      ArrangeObjects(const bool redraw);
   
//--- Устанавливает (1) ширину, (2) высоту поля, (3) всех полей вокруг элемента управления при автоматической прокрутке
   void              SetAutoScrollMarginWidth(const int value)       { this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W,value);  }
   void              SetAutoScrollMarginHeight(const int value)      { this.SetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H,value);  }
   void              SetAutoScrollMarginAll(const int value)
                       {
                        this.SetAutoScrollMarginWidth(value); this.SetAutoScrollMarginHeight(value);
                       }
   void              SetAutoScrollMargin(const int width,const int height)
                       {
                        this.SetAutoScrollMarginWidth(width); this.SetAutoScrollMarginHeight(height);
                       }
//--- Возвращает (1) ширину, (2) высоту поля вокруг элемента управления при автоматической прокрутке
   int               AutoScrollMarginWidth(void)               const { return (int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_W); }
   int               AutoScrollMarginHeight(void)              const { return (int)this.GetProperty(CANV_ELEMENT_PROP_AUTOSCROLL_MARGIN_H); }
  
//--- Устанавливает флаг автоматического изменения размера элемента под содержимое
   virtual void      SetAutoSize(const bool flag,const bool redraw)
                       {
                        bool prev=this.AutoSize();
                        if(prev==flag)
                           return;
                        CWinFormBase::SetAutoSize(flag,redraw);
                        if(prev!=this.AutoSize() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
//--- (1) Устанавливает, (2) возвращает режим автоматического изменения размера элемента под содержимое
   void              SetAutoSizeMode(const ENUM_CANV_ELEMENT_AUTO_SIZE_MODE mode,const bool redraw)
                       {
                        ENUM_CANV_ELEMENT_AUTO_SIZE_MODE prev=this.AutoSizeMode();
                        if(prev==mode)
                           return;
                        this.SetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE,mode);
                        if(prev!=this.AutoSizeMode() && this.ElementsTotal()>0)
                           this.AutoSizeProcess(redraw);
                       }
   ENUM_CANV_ELEMENT_AUTO_SIZE_MODE AutoSizeMode(void)   const { return (ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)this.GetProperty(CANV_ELEMENT_PROP_AUTOSIZE_MODE); }
   
//--- (1) Устанавливает, (2) возвращает режим привязки границ элемента к контейнеру
   virtual void      SetDockMode(const ENUM_CANV_ELEMENT_DOCK_MODE mode,const bool redraw)
                       {
                        if(this.DockMode()==mode)
                           return;
                        CWinFormBase::SetDockMode(mode,redraw);
                        CContainer *base=this.GetBase();
                        if(base!=NULL)
                           base.ArrangeObjects(redraw);
                       }

//--- Устанавливает ширину рамки формы (1) слева, (2) сверху, (3) справа, (4) снизу, (5) всех сторон элемента управления
   virtual void      SetFrameWidthLeft(const uint value)
                       {
                        this.m_frame_width_left=(int)value;
                        if(this.PaddingLeft()<this.FrameWidthLeft())
                           this.SetPaddingLeft(this.FrameWidthLeft());
                       }
   virtual void      SetFrameWidthTop(const uint value)
                       {
                        this.m_frame_width_top=(int)value;
                        if(this.PaddingTop()<this.FrameWidthTop())
                           this.SetPaddingTop(this.FrameWidthTop());
                       }
   virtual void      SetFrameWidthRight(const uint value)
                       {
                        this.m_frame_width_right=(int)value;
                        if(this.PaddingRight()<this.FrameWidthRight())
                           this.SetPaddingRight(this.FrameWidthRight());
                       }
   virtual void      SetFrameWidthBottom(const uint value)
                       {
                        this.m_frame_width_bottom=(int)value;
                        if(this.PaddingBottom()<this.FrameWidthBottom())
                           this.SetPaddingBottom(this.FrameWidthBottom());
                       }
   virtual void      SetFrameWidthAll(const uint value)
                       {
                        this.SetFrameWidthLeft(value); this.SetFrameWidthTop(value); this.SetFrameWidthRight(value); this.SetFrameWidthBottom(value);
                       }

//--- Конструкторы
                     CContainer(const long chart_id,
                                const int subwindow,
                                const string name,
                                const int x,
                                const int y,
                                const int w,
                                const int h);
                     CContainer(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER);
                        this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; 
                        this.SetForeColor(CLR_DEF_FORE_COLOR);
                        this.SetFontBoldType(FW_TYPE_NORMAL);
                        this.SetMarginAll(3);
                        this.SetPaddingAll(0);
                        this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
                        this.SetBorderStyle(FRAME_STYLE_NONE);
                        this.SetAutoScroll(false,false);
                        this.SetAutoScrollMarginAll(0);
                        this.SetAutoSize(false,false);
                        this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
                        this.Initialize();
                       }
//--- Деструктор
                    ~CContainer();
  };
//+------------------------------------------------------------------+

Остальные методы нам уже знакомы по прошлой статье, где рассматривали создание объекта-панели.

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

//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CContainer::CContainer(const long chart_id,
                       const int subwindow,
                       const string name,
                       const int x,
                       const int y,
                       const int w,
                       const int h) : CWinFormBase(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CONTAINER);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CONTAINER);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.SetForeColor(CLR_DEF_FORE_COLOR);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(3);
   this.SetPaddingAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoScroll(false,false);
   this.SetAutoScrollMarginAll(0);
   this.SetAutoSize(false,false);
   this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
   this.Initialize();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Деструктор                                                       |
//+------------------------------------------------------------------+
CContainer::~CContainer()
  {
   CForm::Deinitialize();
  }
//+------------------------------------------------------------------+


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

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

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


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

//+------------------------------------------------------------------+
//| Создаёт новый присоединённый элемент                             |
//+------------------------------------------------------------------+
bool CContainer::CreateNewElement(const ENUM_GRAPH_ELEMENT_TYPE element_type,
                                  CGCnvElement *main,
                                  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,main,x,y,w,h,colour,opacity,activity);
   if(obj==NULL)
      return false;
//--- Устанавливаем созданному объекту цвет текста как у базовой панели
   obj.SetForeColor(this.ForeColor());
//--- Если тип объекта - контейнер
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_CONTAINER)
     {
      //--- устанавливаем цвет рамки равным цвету фона 
      obj.SetColorFrame(obj.ColorBackground());
     }
//--- Если тип объекта - Панель
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_PANEL)
     {
      //--- устанавливаем цвет рамки равным цвету фона 
      obj.SetColorFrame(obj.ColorBackground());
     }
//--- Если тип объекта - GroupBox
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_GROUPBOX)
     {
      //--- устанавливаем цвет рамки равным цвету фона 
      obj.SetColorFrame(obj.ColorBackground());
     }
//--- Если тип объекта - Текстовая метка
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_LABEL)
     {
      //--- устанавливаем цвет текста объекта в зависимости от переданного в метод -
      //--- либо цвет текста панели, либо переданный в метод, и цвет рамки, равный цвету текста 
      obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour);
      obj.SetColorFrame(main!=NULL ? main.ColorBackground() : obj.ForeColor());
     }
//--- Если тип объекта - CheckBox
   if(obj.TypeGraphElement()==GRAPH_ELEMENT_TYPE_WF_CHECKBOX)
     {
      //--- устанавливаем цвет текста объекта в зависимости от переданного в метод -
      //--- либо цвет текста объекта, либо переданный в метод, и цвет рамки, равный цвету текста 
      obj.SetForeColor(colour==clrNONE ? this.ForeColor() : colour);
      obj.SetColorFrame(main!=NULL ? main.ColorBackground() : obj.ForeColor());
     }
//--- Если у панели включено автоизменение размеров и есть привязанные объекты - вызываем метод изменения размеров
   if(this.AutoSize() && this.ElementsTotal()>0)
      this.AutoSizeProcess(redraw);
//--- Перерисовываем панель и все добавленные объекты и возвращаем true
   this.Redraw(redraw);
   return true;
  }
//+------------------------------------------------------------------+

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

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

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

//+------------------------------------------------------------------+
//| Сбрасывает размеры всех привязанных объектов к изначальным       |
//+------------------------------------------------------------------+
bool CContainer::ResetSizeAllToInit(void)
  {
   bool res=true;
   CArrayObj *list=this.GetListWinFormsObj();
   if(list==NULL)
      return false;
   for(int i=0;i<list.Total();i++)
     {
      CWinFormBase *obj=list.At(i);
      if(obj==NULL)
        {
         res &=false;
         continue;
        }
      res &=obj.Resize(i,obj.GetWidthInit(),obj.GetHeightInit());
     }
   return res;
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Подстраивает размеры элемента под его внутреннее содержимое      |
//+------------------------------------------------------------------+
bool CContainer::AutoSizeProcess(const bool redraw)
  {
//--- Получаем список прикреплённых объектов с типом WinForms базовый и выше
   CArrayObj *list=this.GetListWinFormsObj();
//--- Получаем из списка объекты с максимальной и минимальной координатами по X и Y по их индексам
   CWinFormBase *maxx=list.At(CSelect::FindGraphCanvElementMax(list,CANV_ELEMENT_PROP_COORD_X));
   CWinFormBase *minx=list.At(CSelect::FindGraphCanvElementMin(list,CANV_ELEMENT_PROP_COORD_X));
   CWinFormBase *maxy=list.At(CSelect::FindGraphCanvElementMax(list,CANV_ELEMENT_PROP_COORD_Y));
   CWinFormBase *miny=list.At(CSelect::FindGraphCanvElementMin(list,CANV_ELEMENT_PROP_COORD_Y));
//--- Если хотя бы один из четырёх объектов не получен - возвращаем false
   if(maxx==NULL || minx==NULL || maxy==NULL || miny==NULL)
      return false;

//--- Получаем минимальную координату по X и Y
   int min_x=minx.CoordX();
   int min_y=fmin(miny.CoordY(),maxy.BottomEdge());
//--- Рассчитываем общую ширину и высоту всех прикреплённых объектов
   int w=maxx.RightEdge()-min_x;
   int h=int(::fmax(miny.CoordY(),maxy.BottomEdge())-min_y);
//--- Рассчитываем количество пикселей, на которые необходимо изменить размер контейнера по ширине и высоте
   int excess_x=w-this.GetWidthWorkspace();
   int excess_y=h-this.GetHeightWorkspace();
//--- Рассчитываем смещение, на которое необходимо будет сместить прикреплённые объекты
   int shift_x=this.GetCoordXWorkspace()-min_x;
   int shift_y=this.GetCoordYWorkspace()-min_y;
//--- Если размеры контейнера менять не нужно - возвращаем true
   if(excess_x==0 && excess_y==0)
      return true;
//--- Если необходимо сместить прикреплённые объекты внутри контейнера по координате X или Y
   bool res=true;
   if(shift_x>0 || shift_y>0)
     {
      //--- В цикле по всем прикреплённым объектам
      for(int i=0;i<list.Total();i++)
        {
         //--- получаем очередной объект
         CWinFormBase *obj=list.At(i);
         if(obj==NULL)
            continue;
         //--- Если объект нужно сместить по горизонтали - записываем в res результат смещения
         if(shift_x>0)
            res &=obj.Move(obj.CoordX()+shift_x,obj.CoordY());
         //--- Если объект нужно сместить по вертикали - записываем в res результат смещения
         if(shift_y>0)
            res &=obj.Move(obj.CoordX(),obj.CoordY()+shift_y);
         //--- Устанавливаем новые относительные координаты объекта по X и Y
         obj.SetCoordXRelative(obj.CoordX()-this.GetCoordXWorkspace());
         obj.SetCoordYRelative(obj.CoordY()-this.GetCoordYWorkspace());
        }
     }
//--- Возвращаем результат изменения размеров контейнера
   return
     (
      //--- Если не удалось сместить хотя бы один привязанный объект - возвращаем false
      !res ? false :
      //--- Иначе, если только увеличение размеров
      this.AutoSizeMode()==CANV_ELEMENT_AUTO_SIZE_MODE_GROW ? 
      this.Resize(this.Width()+(excess_x>0  ? excess_x : 0),this.Height()+(excess_y>0  ? excess_y : 0),redraw) :
      //--- если увеличение и уменьшение размеров
      this.Resize(this.Width()+(excess_x!=0 ? excess_x : 0),this.Height()+(excess_y!=0 ? excess_y : 0),redraw)
     );
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Располагает привязанные объекты в порядке их 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.GetWidthWorkspace(),obj.GetHeightInit(),false))
            continue;
         //--- Получаем координаты привязки объекта
         x=this.GetCoordXWorkspace();
         y=(prev!=NULL ? prev.BottomEdge()+1 : this.GetCoordYWorkspace());
         //--- Если не удалось сместить объект на полученные координаты - идём к следующему
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Привязка снизу
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_BOTTOM)
        {
         //--- Если не удалось изменить размеры объекта (на всю ширину рабочей области и на изначальную высоту объекта) - идём к следующему
         if(!obj.Resize(this.GetWidthWorkspace(),obj.GetHeightInit(),false))
            continue;
         //--- Получаем координаты привязки объекта
         x=this.GetCoordXWorkspace();
         y=(prev!=NULL ? prev.CoordY()-obj.Height()-1 : this.GetBottomEdgeWorkspace()-obj.Height()-1);
         //--- Если не удалось сместить объект на полученные координаты - идём к следующему
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Привязка слева
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_LEFT)
        {
         //--- Если не удалось изменить размеры объекта (на изначальную ширину объекта и на всю высоту рабочей области) - идём к следующему
         if(!obj.Resize(obj.GetWidthInit(),this.GetHeightWorkspace(),false))
            continue;
         //--- Получаем координаты привязки объекта
         x=(prev!=NULL ? prev.RightEdge()+1 : this.GetCoordXWorkspace());
         y=this.GetCoordYWorkspace();
         //--- Если не удалось сместить объект на полученные координаты - идём к следующему
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Привязка справа
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_RIGHT)
        {
         //--- Если не удалось изменить размеры объекта (на изначальную ширину объекта и на всю высоту рабочей области) - идём к следующему
         if(!obj.Resize(obj.GetWidthInit(),this.GetHeightWorkspace(),false))
            continue;
         //--- Получаем координаты привязки объекта
         x=(prev!=NULL ? prev.CoordX()-obj.Width()-1 : this.GetRightEdgeWorkspace()-obj.Width());
         y=this.GetCoordYWorkspace();
         //--- Если не удалось сместить объект на полученные координаты - идём к следующему
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Привязка с заливкой
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_FILL)
        {
         //--- Если не удалось изменить размеры объекта (на всю ширину и высоту рабочей области) - идём к следующему
         if(!obj.Resize(this.GetWidthWorkspace(),this.GetHeightWorkspace(),false))
            continue;
         //--- Получаем координаты привязки объекта
         x=this.GetCoordXWorkspace();
         y=this.GetCoordYWorkspace();
         //--- Если не удалось сместить объект на полученные координаты - идём к следующему
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Нет привязки
      if(obj.DockMode()==CANV_ELEMENT_DOCK_MODE_NONE)
        {
         //--- Изменяем размеры объекта на изначальные
         obj.Resize(obj.GetWidthInit(),obj.GetHeightInit(),false);
         //--- Получаем изначальные координаты расположения объекта
         x=this.GetCoordXWorkspace()+obj.CoordXRelativeInit();
         y=this.GetCoordYWorkspace()+obj.CoordYRelativeInit();
         //--- Если не удалось сместить объект на полученные координаты - идём к следующему
         if(!obj.Move(x,y,false))
            continue;
        }
      //--- Рассчитываем и устанавливаем относительные координаты объекта
      obj.SetCoordXRelative(x-this.GetCoordXWorkspace());
      obj.SetCoordYRelative(y-this.GetCoordYWorkspace());
     }

//--- Если включен режим автоизменения размеров
   if(this.AutoSize())
      this.AutoSizeProcess(false);

//--- Перерисуем объект с флагом перерисовки и вернём true
   this.Redraw(redraw); 
   return true;
  }
//+------------------------------------------------------------------+

Оба представленные выше метода требуют переработки, и поэтому здесь они есть лишь по причине, что были в классе объекта-панели. Но будут впоследствии переработаны.

Методы, представленные ниже, тоже перенесены из класса объекта-панели. Перенесены без изменений:

//+------------------------------------------------------------------+
//| Возвращает список прикреплённых объектов                         |
//| с любым типом WinForms базовый и выше                            |
//+------------------------------------------------------------------+
CArrayObj *CContainer::GetListWinFormsObj(void)
  {
   return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_BASE,EQUAL_OR_MORE);
  }
//+------------------------------------------------------------------+
//| Возвращает список прикреплённых объектов                         |
//| с указанным типом WinForms объекта                               |
//+------------------------------------------------------------------+
CArrayObj *CContainer::GetListWinFormsObjByType(const ENUM_GRAPH_ELEMENT_TYPE type)
  {
   return CSelect::ByGraphCanvElementProperty(this.GetListElements(),CANV_ELEMENT_PROP_TYPE,type,EQUAL);
  }
//+------------------------------------------------------------------+
//| Возвращает указатель на прикреплённый WinForms-объект            |
//| с указанным типом по индексу                                     |
//+------------------------------------------------------------------+
CWinFormBase *CContainer::GetWinFormsObj(const ENUM_GRAPH_ELEMENT_TYPE type,const int index)
  {
   CArrayObj *list=this.GetListWinFormsObjByType(type);
   return(list!=NULL ? list.At(index) : NULL);
  }
//+------------------------------------------------------------------+
//| Рассчитывает координаты привязки Dock-объектов                   |
//+------------------------------------------------------------------+
void CContainer::CalculateCoords(CArrayObj *list)
  {
   
  }
//+------------------------------------------------------------------+

Мы полностью рассмотрели базовый класс объектов-контейнеров.

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


WinForms-объект GroupBox

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

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

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

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

public:
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   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);
//--- Устанавливает стиль рамки
   virtual void      SetBorderStyle(const ENUM_FRAME_STYLE style)
                       {
                        if((this.FrameWidthTop()<2 || this.FrameWidthBottom()<2 || this.FrameWidthLeft()<2 || this.FrameWidthRight()<2) && 
                            style>FRAME_STYLE_FLAT)
                           this.SetFrameWidthAll(2);
                        this.SetProperty(CANV_ELEMENT_PROP_BORDER_STYLE,style);
                       }
   
//--- Конструкторы
                     CGroupBox(const long chart_id,
                               const int subwindow,
                               const string name,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
                     CGroupBox(const string name) : CContainer(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                        this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
                        this.Initialize();
                       }
//--- Деструктор
                    ~CGroupBox(){ CForm::Deinitialize(); }
  };
//+------------------------------------------------------------------+

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


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

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

Как можно заметить, здесь тип WinForms-объекта записывается как GroupBox, а тип объекта библиотеки — как контейнер.


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

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


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

//+------------------------------------------------------------------+
//| Инициализирует переменные                                        |
//+------------------------------------------------------------------+
void CGroupBox::Initialize(void)
  {
//--- Очищаем все списки объекта и устанавливаем им флаги сортированных списков
   this.m_list_elements.Clear();
   this.m_list_elements.Sort();
   this.m_list_tmp.Clear();
   this.m_list_tmp.Sort();
//--- Объекта тени нет у GroupBox
   this.m_shadow_obj=NULL;
   this.m_shadow=false;
//--- Ширина рамки объекта с каждой его стороны по умолчанию равна 1 пиксель
   this.SetFrameWidth(1,1,1,1);
//--- У объекта нет градиентной заливки (как вертикальной, так и горизонтальной)
   this.m_gradient_v=false;
   this.m_gradient_c=false;
//--- Сбросим все "рабочие" флаги и переменные
   this.m_mouse_state_flags=0;
   this.m_offset_x=0;
   this.m_offset_y=0;
   CGCnvElement::SetInteraction(false);
//--- Создадим объект анимации и добавим его в список для хранения таких объектов
   this.m_animations=new CAnimations(CGCnvElement::GetObject());
   this.m_list_tmp.Add(this.m_animations);
//--- Устанавливаем прозрачный цвет для фона объекта и цвет по умолчанию для рамки
   this.SetColorBackground(CLR_CANV_NULL);
   this.SetOpacity(0);
   this.SetColorFrame(CLR_DEF_FRAME_GBOX_COLOR);
//--- Установим цвет и непрозрачность текста по умолчанию и отсутствие рамки объекта
   this.SetForeColor(CLR_DEF_FORE_COLOR);
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetBorderStyle(FRAME_STYLE_SIMPLE);
//--- Установим параметры текста по умолчанию
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.SetText("GroupBox");
   this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP);
   this.SetTextAlign(ANCHOR_LEFT_UPPER);
//--- Установим параметры объекта по умолчанию
   this.SetAutoSize(false,false);
   this.SetMarginAll(3);
   this.SetPaddingAll(3);
   this.SetEnabled(true);
   this.SetVisible(true,false);
  }
//+------------------------------------------------------------------+

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

Методы для очистки объекта:

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

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

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

//+------------------------------------------------------------------+
//| Рисует рамку                                                     |
//+------------------------------------------------------------------+
void CGroupBox::DrawFrame(void)
  {
//--- Получаем половину высоты текста
   int w=0;
   int h=0;
   this.TextSize(Text(),w,h);
   int height=this.Height()-h/2;
//--- В зависимости от стиля рамки рисуем нужный её тип
   switch(this.BorderStyle())
     {
      case FRAME_STYLE_FLAT :
        this.DrawFrameFlat(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity());
        break;
      case FRAME_STYLE_BEVEL :
        this.DrawFrameBevel(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity());
        break;
      case FRAME_STYLE_STAMP :
        this.DrawFrameStamp(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity());
        break;
      //--- FRAME_STYLE_SIMPLE
      default:
        this.DrawFrameSimple(0,h/2,this.Width(),height,this.FrameWidthTop(),this.FrameWidthBottom(),this.FrameWidthLeft(),this.FrameWidthRight(),this.ColorFrame(),this.ForeColorOpacity());
        break;
     }
//--- Если текст, установленный для объекта, не пустая строка, стираем прозрачным цветом область рамки, где должен располагаться текст
   if(this.Text()!="")
      this.DrawRectangleFill(5,h/2-1,w+7,h/2+this.FrameWidthTop()+1,CLR_CANV_NULL,0);
  }
//+------------------------------------------------------------------+

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

WinForms-объект GroupBox готов.

Откроем файл класса объекта-панели\MQL5\Include\DoEasy\Objects\Graph\WForms\Containers\Panel.mqh и удалим из него все перенесённые в базовый класс методы. Кроме того, подключим к файлу класса файл базового класса объектов-контейнеров и файл класса GroupBox. Класс теперь будет унаследован от базового объекта-кнтейнера:

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

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

class CPanel : public CWinFormBase
  {
private:
   CGCnvElement     *m_obj_top;                                      // Указатель на объект, к координатам которого привязан текущий сверху
   CGCnvElement     *m_obj_bottom;                                   // Указатель на объект, к координатам которого привязан текущий снизу
   CGCnvElement     *m_obj_left;                                     // Указатель на объект, к координатам которого привязан текущий слева
   CGCnvElement     *m_obj_right;                                    // Указатель на объект, к координатам которого привязан текущий справа
   CGCnvElement     *m_underlay;                                     // Подложка для размещения элементов

//--- Создаёт новый графический объект
   virtual CGCnvElement *CreateNewGObject(const ENUM_GRAPH_ELEMENT_TYPE type,
                                          const int element_num,
                                          const string name,
                                          const int x,
                                          const int y,
                                          const int w,
                                          const int h,
                                          const color colour,
                                          const uchar opacity,
                                          const bool movable,
                                          const bool activity);
//--- Возвращает начальные координаты привязанного объекта
   virtual void      GetCoords(int &x,int &y);
//--- Создаёт объект-подложку
   bool              CreateUnderlayObj(void);
//--- Устанавливает подложку началом отсчёта координат
   void              SetUnderlayAsBase(void);


Из конструкторов класса удалим лишние строки, так как теперь они прописаны в родительском классе:

//--- Конструкторы
                     CPanel(const long chart_id,
                            const int subwindow,
                            const string name,
                            const int x,
                            const int y,
                            const int w,
                            const int h);
                     CPanel(const string name) : CWinFormBase(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_PANEL; 
                        this.SetForeColor(CLR_DEF_FORE_COLOR);
                        this.SetFontBoldType(FW_TYPE_NORMAL);
                        this.SetMarginAll(3);
                        this.SetPaddingAll(0);
                        this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
                        this.SetBorderStyle(FRAME_STYLE_NONE);
                        this.SetAutoScroll(false,false);
                        this.SetAutoScrollMarginAll(0);
                        this.SetAutoSize(false,false);
                        this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
                        this.Initialize();
                        if(this.CreateUnderlayObj())
                           this.SetUnderlayAsBase();
                       }
//--- Деструктор
                    ~CPanel();
  };
//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CPanel::CPanel(const long chart_id,
               const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CWinFormBase(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_PANEL;
   this.SetForeColor(CLR_DEF_FORE_COLOR);
   this.SetFontBoldType(FW_TYPE_NORMAL);
   this.SetMarginAll(3);
   this.SetPaddingAll(0);
   this.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
   this.SetBorderStyle(FRAME_STYLE_NONE);
   this.SetAutoScroll(false,false);
   this.SetAutoScrollMarginAll(0);
   this.SetAutoSize(false,false);
   this.SetAutoSizeMode(CANV_ELEMENT_AUTO_SIZE_MODE_GROW,false);
   this.Initialize();
   if(this.CreateUnderlayObj())
      this.SetUnderlayAsBase();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+

Кроме того, в конструкторах пропишем новый тип объекта и инициализацию класса CContainer вместо ранее CWinFormBase в списке инициализации:

//--- Конструкторы
                     CPanel(const long chart_id,
                            const int subwindow,
                            const string name,
                            const int x,
                            const int y,
                            const int w,
                            const int h);
                     CPanel(const string name) : CContainer(::ChartID(),0,name,0,0,0,0)
                       {
                        CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL);
                        CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL);
                        this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER; 
                        this.CreateUnderlayObj();
                       }
//--- Деструктор
                    ~CPanel(){ CForm::Deinitialize(); }
  };
//+------------------------------------------------------------------+
//| Конструктор с указанием идентификатора чарта и подокна           |
//+------------------------------------------------------------------+
CPanel::CPanel(const long chart_id,
               const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CContainer(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_PANEL);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_PANEL);
   this.m_type=OBJECT_DE_TYPE_GWF_CONTAINER;
   this.CreateUnderlayObj();
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetWidthInit(w);
   this.SetHeightInit(h);
  }
//+------------------------------------------------------------------+


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

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

Объект CheckBox — его создание, рассмотрим здесь чуть позже.

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

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


Базовый объект в категории "Стандартные элементы управления"

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

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

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


Класс совсем небольшой. Рассмотрим целиком его тело:

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

protected:
//--- Автоматически устанавливает ширину и высоту элемента
   virtual void      AutoSetWH(void)   { return;   }
//--- Инициализирует переменные
   virtual void      Initialize(void);
   
public:
//--- Очищает элемент с заполнением его цветом и непрозрачностью
   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);

//--- Конструктор
                     CCommonBase(const long chart_id,
                                 const int subwindow,
                                 const string name,
                                 const int x,
                                 const int y,
                                 const int w,
                                 const int h);
  };
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CCommonBase::CCommonBase(const long chart_id,
                         const int subwindow,
                         const string name,
                         const int x,
                         const int y,
                         const int w,
                         const int h) : CWinFormBase(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_COMMON_BASE);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_COMMON_BASE);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetCoordX(x);
   this.SetCoordY(y);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   if(this.AutoSize())
      this.AutoSetWH();
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+


Метод инициализации:

//+------------------------------------------------------------------+
//| Инициализирует переменные                                        |
//+------------------------------------------------------------------+
void CCommonBase::Initialize(void)
  {
//--- Очищаем все списки объекта и устанавливаем им флаги сортированных списков
   this.m_list_elements.Clear();
   this.m_list_elements.Sort();
   this.m_list_tmp.Clear();
   this.m_list_tmp.Sort();
//--- Объекта тени нет у стандартного элемента управления
   this.m_shadow_obj=NULL;
   this.m_shadow=false;
//--- Ширина рамки объекта с каждой его стороны по умолчанию равна 1 пиксель
   this.m_frame_width_right=1;
   this.m_frame_width_left=1;
   this.m_frame_width_top=1;
   this.m_frame_width_bottom=1;
//--- У объекта нет градиентной заливки (как вертикальной, так и горизонтальной)
   this.m_gradient_v=false;
   this.m_gradient_c=false;
//--- Сбросим все "рабочие" флаги и переменные
   this.m_mouse_state_flags=0;
   this.m_offset_x=0;
   this.m_offset_y=0;
   CGCnvElement::SetInteraction(false);
//--- Создадим объект анимации и добавим его в список для хранения таких объектов
   this.m_animations=new CAnimations(CGCnvElement::GetObject());
   this.m_list_tmp.Add(this.m_animations);
//--- Устанавливаем прозрачный цвет для фона объекта
   this.SetColorBackground(CLR_CANV_NULL);
   this.SetOpacity(0);
//--- Установим цвет и непрозрачность текста по умолчанию и отсутствие рамки объекта
   this.SetForeColor(CLR_DEF_FORE_COLOR);
   this.SetForeColorOpacity(CLR_DEF_FORE_COLOR_OPACITY);
   this.SetBorderStyle(FRAME_STYLE_NONE);
//--- Установим параметры текста по умолчанию
   this.SetFont(DEF_FONT,DEF_FONT_SIZE);
   this.SetText("");
   this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP);
   this.SetTextAlign(ANCHOR_LEFT_UPPER);
//--- Установим параметры объекта по умолчанию
   this.SetAutoSize(false,false);
   this.SetMarginAll(3);
   this.SetPaddingAll(0);
   this.SetEnabled(true);
   this.SetVisible(true,false);
  }
//+------------------------------------------------------------------+


Методы очистки и закрашивания фона объекта:

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

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

Так как мы перенесли некоторые методы из класса объекта "Текстовая метка" в класс базового объекта стандартных элементов управления, то нам нужно подправить класс CLabel в файле \MQL5\Include\DoEasy\Objects\Graph\WForms\Common Controls\Label.mqh.

Вместо файла WinFormBase.mqh" подключим к классу файл базового объекта и унаследуем класс от него:

//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "CommonBase.mqh"
//+------------------------------------------------------------------+
//| Класс объекта Label элементов управления WForms                  |
//+------------------------------------------------------------------+
class CLabel : public CCommonBase


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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CLabel::CLabel(const long chart_id,
               const int subwindow,
               const string name,
               const int x,
               const int y,
               const int w,
               const int h) : CCommonBase(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_LABEL);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_LABEL);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetCoordX(x);
   this.SetCoordY(y);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   this.SetMargin(3,0,3,0);
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.Redraw(false);
  }
//+------------------------------------------------------------------+

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


WinForms-объект CheckBox

Объект CheckBox представляет из себя флажок выбора с подписью. Подпись и флажок могут находиться каждый в девяти положениях относительно границ объекта:

  • Слева-вверху
  • Слева по центру
  • Слева-внизу
  • Снизу по центру
  • Справа-внизу
  • Справа по центру
  • Справа-вверху
  • Сверху по центру
  • По центру

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

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

  • Не установленный
  • Установленный
  • Неопределённый

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

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

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


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

class CCheckBox : public CLabel
  {
private:
   int               m_text_x;                                       // Координата X текста
   int               m_text_y;                                       // Координата Y текста
   int               m_check_x;                                      // Координата X флажка
   int               m_check_y;                                      // Координата Y флажка
   int               m_check_w;                                      // Ширина флажка
   int               m_check_h;                                      // Высота флажка
//--- Автоматически устанавливает ширину и высоту элемента
   virtual void      AutoSetWH(void);
//--- Устанавливает координаты X и Y (1) флажка проверки, (2) текста в зависимости от типа выравнивания
   void              SetCheckFlagCoords(int &x,int &y);
   void              SetTextCoords(int &x,int &y);
//--- Устанавливает сорректированные координаты текста в зависимости от выравнивания текста и флажка проверки
   void              SetCorrectTextCoords(void);

protected:

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

protected:
//--- Отображает флажок проверки по указанному состоянию
   virtual void      ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state);
   
//--- (1) Устанавливает, (2) возвращает размеры флажка проверки на элементе
   void              SetCheckWidth(const int width)                  { this.m_check_w=(width<5  ? 5 : width);  }
   void              SetCheckHeight(const int height)                { this.m_check_h=(height<5 ? 5 : height); }
   int               CheckWidth(void)                          const { return this.m_check_w;                  }
   int               CheckHeight(void)                         const { return this.m_check_h;                  }
   
public:
//--- Устанавливает (1) ширину, (2) высоту элемента,
   virtual bool      SetWidth(const int width)                       { return CGCnvElement::SetWidth(width>this.m_check_w   ? width  : this.m_check_w);     }
   virtual bool      SetHeight(const int height)                     { return CGCnvElement::SetHeight(height>this.m_check_h ? height : this.m_check_h);     }
   
//--- (1) Устанавливает, (2) возвращает угол расположения (тип выравнивания) флажка проверки на элементе
   void              SetCheckAlign(const ENUM_ANCHOR_POINT anchor)   { this.SetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN,anchor);                              }
   ENUM_ANCHOR_POINT CheckAlign(void)                          const { return (ENUM_ANCHOR_POINT)this.GetProperty(CANV_ELEMENT_PROP_CHECK_ALIGN);           }
   
//--- (1) Устанавливает, (2) возвращает состояние флажка проверки
   void              SetChecked(const bool flag)                     { this.SetProperty(CANV_ELEMENT_PROP_CHECKED,flag);                                    }
   bool              Checked(void)                             const { return (bool)this.GetProperty(CANV_ELEMENT_PROP_CHECKED);                            }
   
//--- (1) Устанавливает, (2) возвращает состояние элемента управления
   void              SetCheckState(const ENUM_CANV_ELEMENT_CHEK_STATE state) { this.SetProperty(CANV_ELEMENT_PROP_CHECK_STATE,state);                       }
   ENUM_CANV_ELEMENT_CHEK_STATE CheckState(void)               const { return (ENUM_CANV_ELEMENT_CHEK_STATE)this.GetProperty(CANV_ELEMENT_PROP_CHECK_STATE);}

//--- Перерисовывает объект
   virtual void      Redraw(bool redraw);

//--- Конструктор
                     CCheckBox(const long chart_id,
                               const int subwindow,
                               const string name,
                               const int x,
                               const int y,
                               const int w,
                               const int h);
  };
//+------------------------------------------------------------------+

Все эти методы озаглавлены в описаниях методов и, думаю, понятны. Рассмотрим их реализацию.

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

//+------------------------------------------------------------------+
//| Конструктор                                                      |
//+------------------------------------------------------------------+
CCheckBox::CCheckBox(const long chart_id,
                     const int subwindow,
                     const string name,
                     const int x,
                     const int y,
                     const int w,
                     const int h) : CLabel(chart_id,subwindow,name,x,y,w,h)
  {
   CGBaseObj::SetTypeElement(GRAPH_ELEMENT_TYPE_WF_CHECKBOX);
   CGCnvElement::SetProperty(CANV_ELEMENT_PROP_TYPE,GRAPH_ELEMENT_TYPE_WF_CHECKBOX);
   this.m_type=OBJECT_DE_TYPE_GWF_COMMON;
   this.SetCoordX(x);
   this.SetCoordY(y);
   this.SetCheckWidth(DEF_CHECK_SIZE);
   this.SetCheckHeight(DEF_CHECK_SIZE);
   this.SetWidth(w);
   this.SetHeight(h);
   this.Initialize();
   this.SetWidthInit(this.Width());
   this.SetHeightInit(this.Height());
   this.SetCoordXInit(x);
   this.SetCoordYInit(y);
   this.SetTextAlign(ANCHOR_LEFT);
   this.m_text_x=0;
   this.m_text_y=0;
   this.m_check_x=0;
   this.m_check_y=0;
   this.Redraw(false);
  }
//+------------------------------------------------------------------+


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

//+------------------------------------------------------------------+
//| Перерисовывает объект                                            |
//+------------------------------------------------------------------+
void CCheckBox::Redraw(bool redraw)
  {
//--- Заполняем объект цветом фона с полной прозрачностью
   this.Erase(this.ColorBackground(),0,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.Update(redraw);
  }
//+------------------------------------------------------------------+

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


Метод, устанавливающий координаты X и Y флажка проверки в зависимости от типа выравнивания:

//+------------------------------------------------------------------+
//| Устанавливает координаты X и Y флажка проверки                   |
//| в зависимости от типа выравнивания                               |
//+------------------------------------------------------------------+
void CCheckBox::SetCheckFlagCoords(int &x,int &y)
  {
//--- В зависимости от расположения флажка проверки
   switch(this.CheckAlign())
     {
      //--- Флажок расположен от левой грани объекта в центре по вертикали
      case ANCHOR_LEFT : 
        x=0;
        y=(this.Height()-this.CheckHeight())/2;
        break;
      //--- Флажок расположен в левом нижнем углу объекта
      case ANCHOR_LEFT_LOWER : 
        x=0;
        y=this.Height()-this.CheckHeight()-1;
        break;
      //--- Флажок расположен по центру нижней грани объекта
      case ANCHOR_LOWER : 
        x=(this.Width()-this.CheckWidth())/2;
        y=this.Height()-this.CheckHeight()-1;
        break;
      //--- Флажок расположен в правом нижнем углу объекта
      case ANCHOR_RIGHT_LOWER : 
        x=this.Width()-this.CheckWidth()-1;
        y=this.Height()-this.CheckHeight()-1;
        break;
      //--- Флажок расположен от правой грани объекта в центре по вертикали
      case ANCHOR_RIGHT : 
        x=this.Width()-this.CheckWidth()-1;
        y=(this.Height()-this.CheckHeight())/2;
        break;
      //--- Флажок расположен в правом верхнем углу объекта
      case ANCHOR_RIGHT_UPPER : 
        x=this.Width()-this.CheckWidth()-1;
        y=0;
        break;
      //--- Флажок расположен по центру верхней грани объекта
      case ANCHOR_UPPER : 
        x=(this.Width()-this.CheckWidth())/2;
        y=0;
        break;
      //--- Флажок расположен по центру объекта
      case ANCHOR_CENTER : 
        x=(this.Width()-this.CheckWidth())/2;
        y=(this.Height()-this.CheckHeight())/2;
        break;
      //--- Флажок расположен в левом верхнем углу объекта
      //---ANCHOR_LEFT_UPPER
      default:
        x=0;
        y=0;
        break;
     }
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Устанавливает координаты X и Y текста                            |
//| в зависимости от типа выравнивания                               |
//+------------------------------------------------------------------+
void CCheckBox::SetTextCoords(int &x,int &y)
  {
//--- В зависимости от типа выравнивания текста на элементе
   switch(this.TextAlign())
     {
      //--- Текст рисуется в левом верхнем углу объекта
      case ANCHOR_LEFT_UPPER : 
        //--- Устанавливаем точку привязки текста слева вверху
        this.SetTextAnchor(FRAME_ANCHOR_LEFT_TOP);
        //--- Устанавливаем координату точки привязки текста
        x=this.FrameWidthLeft();
        y=this.FrameWidthTop();
        break;
      //--- Текст рисуется от левой грани объекта в центре по вертикали
      case ANCHOR_LEFT : 
        //--- Устанавливаем точку привязки текста слева в центре
        this.SetTextAnchor(FRAME_ANCHOR_LEFT_CENTER);
        //--- Устанавливаем координату точки привязки текста
        x=this.FrameWidthLeft();
        y=this.Height()/2;
        break;
      //--- Текст рисуется в левом нижнем углу объекта
      case ANCHOR_LEFT_LOWER : 
        //--- Устанавливаем точку привязки текста слева внизу
        this.SetTextAnchor(FRAME_ANCHOR_LEFT_BOTTOM);
        //--- Устанавливаем координату точки привязки текста
        x=this.FrameWidthLeft();
        y=this.Height()-this.FrameWidthBottom();
        break;
      
      //--- Текст рисуется по центру нижней грани объекта
      case ANCHOR_LOWER : 
        //--- Устанавливаем точку привязки текста снизу в центре
        this.SetTextAnchor(FRAME_ANCHOR_CENTER_BOTTOM);
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()/2;
        y=this.Height()-this.FrameWidthBottom();
        break;
      //--- Текст рисуется в правом нижнем углу объекта
      case ANCHOR_RIGHT_LOWER : 
        //--- Устанавливаем точку привязки текста справа внизу
        this.SetTextAnchor(FRAME_ANCHOR_RIGHT_BOTTOM);
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()-this.FrameWidthRight();
        y=this.Height()-this.FrameWidthBottom();
        break;
      //--- Текст рисуется от правой грани объекта в центре по вертикали
      case ANCHOR_RIGHT : 
        //--- Устанавливаем точку привязки текста справа в центре
        this.SetTextAnchor(FRAME_ANCHOR_RIGHT_CENTER);
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()-this.FrameWidthRight();
        y=this.Height()/2;
        break;
      //--- Текст рисуется в правом верхнем углу объекта
      case ANCHOR_RIGHT_UPPER : 
        //--- Устанавливаем точку привязки текста справа вверху
        this.SetTextAnchor(FRAME_ANCHOR_RIGHT_TOP);
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()-this.FrameWidthRight();
        y=this.FrameWidthTop();
        break;
      //--- Текст рисуется по центру верхней грани объекта
      case ANCHOR_UPPER : 
        //--- Устанавливаем точку привязки текста сверху в центре
        this.SetTextAnchor(FRAME_ANCHOR_CENTER_TOP);
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()/2;
        y=this.FrameWidthTop();
        break;
      //--- Текст рисуется по центру объекта
      //---ANCHOR_CENTER
      default:
        //--- Устанавливаем точку привязки текста в центре
        this.SetTextAnchor(FRAME_ANCHOR_CENTER);
        //--- Устанавливаем координату точки привязки текста
        x=this.Width()/2;
        y=this.Height()/2;
        break;
     }
  }
//+------------------------------------------------------------------+

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


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

//+------------------------------------------------------------------+
//| Устанавливает корректные координаты текста в зависимости         |
//| от выравнивания текста и флажка проверки                         |
//+------------------------------------------------------------------+
void CCheckBox::SetCorrectTextCoords(void)
  {
//--- Устанавливаем координаты флажка и текста в зависимости от способа их выравнивания
   this.SetCheckFlagCoords(this.m_check_x,this.m_check_y);
   this.SetTextCoords(this.m_text_x,this.m_text_y);
//--- Получаем размеры текста
   int text_w=0, text_h=0;
   this.TextSize(this.Text(),text_w,text_h);
//--- В зависимости от расположения флажка проверки в границах объекта
   switch(this.CheckAlign())
     {
      //--- Флажок расположен в левом верхнем углу объекта
      //--- Флажок расположен от левой грани объекта в центре по вертикали
      //--- Флажок расположен в левом нижнем углу объекта
      case ANCHOR_LEFT_UPPER  : 
      case ANCHOR_LEFT        : 
      case ANCHOR_LEFT_LOWER  : 
        //--- Если текст выравнивается по левому краю - устанавливаем координату X текста на ширину флажка проверки + 2 пикселя
        if(this.TextAlign()==ANCHOR_LEFT_UPPER || this.TextAlign()==ANCHOR_LEFT || this.TextAlign()==ANCHOR_LEFT_LOWER)
           this.m_text_x=this.CheckWidth()+2;
        break;

      //--- Флажок расположен в правом верхнем углу объекта
      //--- Флажок расположен от правой грани объекта в центре по вертикали
      //--- Флажок расположен в правом нижнем углу объекта
      case ANCHOR_RIGHT_UPPER : 
      case ANCHOR_RIGHT       : 
      case ANCHOR_RIGHT_LOWER : 
        //--- Если текст выравнивается по правому краю - устанавливаем координату X текста на ширину флажка проверки + 2 пикселя
        if(this.TextAlign()==ANCHOR_RIGHT_UPPER || this.TextAlign()==ANCHOR_RIGHT || this.TextAlign()==ANCHOR_RIGHT_LOWER)
           this.m_text_x=this.Width()-this.CheckWidth()-2;
        break;

      //--- Флажок расположен по центру нижней грани объекта
      case ANCHOR_LOWER : 
        //--- Если текст выравнивается по нижнему краю - устанавливаем координату Y текста на высоту флажка проверки
        if(this.TextAlign()==ANCHOR_LEFT_LOWER || this.TextAlign()==ANCHOR_LOWER || this.TextAlign()==ANCHOR_RIGHT_LOWER)
           this.m_text_y=this.m_check_y;
        break;
      
      //--- Флажок расположен по центру верхней грани объекта
      case ANCHOR_UPPER : 
        //--- Если текст выравнивается по верхнему краю - устанавливаем координату Y текста на высоту флажка проверки
        if(this.TextAlign()==ANCHOR_LEFT_UPPER || this.TextAlign()==ANCHOR_UPPER || this.TextAlign()==ANCHOR_RIGHT_UPPER)
           this.m_text_y=this.m_check_h;
        break;
      //--- Флажок расположен по центру объекта
      //---ANCHOR_CENTER
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Отображает флажок проверки по указанному состоянию               |
//+------------------------------------------------------------------+
void CCheckBox::ShowControlFlag(const ENUM_CANV_ELEMENT_CHEK_STATE state)
  {
//--- Рисуем прямоугольник границ флажка
   this.DrawRectangle(this.m_check_x,this.m_check_y,this.m_check_x+this.CheckWidth(),this.m_check_y+this.CheckHeight(),this.ColorFrame());
//--- Создаём массивы координат X и Y для рисования ломанной линии
   int array_x[]={m_check_x+2,m_check_x+m_check_w/2-1,m_check_x+m_check_w-2};
   int array_y[]={m_check_y+m_check_h/2,m_check_y+m_check_h-3,m_check_y+3};
//--- В зависимости от переданног в метод состояния флажка
   switch(state)
     {
      //--- Установленныйфлажок
      case CANV_ELEMENT_CHEK_STATE_CHECKED :
        //--- Рисуем ломанную линию в виде галочки внутри границ флажка
        this.DrawPolylineAA(array_x,array_y,ColorFrame());
        break;
      //--- Не установленный флажок
      case CANV_ELEMENT_CHEK_STATE_INDETERMINATE :
        //--- Рисуем закрашенный прямоугольник внутри границ флажка
        this.DrawRectangleFill(m_check_x+3,m_check_y+3,m_check_x+m_check_w-3,m_check_y+m_check_h-3,ColorFrame());
        break;
      //--- Неопределённое состояние
      default:
        break;
     }
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//| Автоматически устанавливает ширину и высоту элемента             |
//+------------------------------------------------------------------+
void CCheckBox::AutoSetWH(void)
  {
//--- Определяем переменные для получения ширины и высоты надписи
   int w=0, h=0;
//--- Получаем ширину и высоту в зависимости от текста объекта
   CGCnvElement::TextSize(this.Text()!="" && this.Text()!=NULL ? this.Text() : " ",w,h);
//--- Прибавляем к полученной ширине значения Margin объекта слева и справа и размер флажка проверки
   w+=(this.MarginLeft()+this.MarginRight()+this.CheckWidth());
//--- Если ширина равна размеру флажка проверки - устанавливаем её в три пикселя + размер флажка проверки
   if(w==this.CheckWidth())
      w=this.CheckWidth()+3;
//--- Прибавляем к полученной высоте значения Margin объекта сверху и снизу
   h+=(this.MarginTop()+this.MarginBottom());
//--- Если высоту получить не удалось - устанавливаем её как величину "размер шрифта" * коэффициент
   if(h==0)
      h=(int)ceil(FontSize()*1.625);
//--- Если высота в итоге меньше размера флажка проверки - устанавливаем высоту равной высоте флажка проверки
   if(h<this.CheckHeight())
      h=this.CheckHeight();
//--- Устанавливаем ширину и высоту объекта из полученных значений
   this.SetWidth(w);
   this.SetHeight(h);
  }
//+------------------------------------------------------------------+

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

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

//+------------------------------------------------------------------+
//|                                                    Container.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 "..\..\WForms\WinFormBase.mqh"
#include "..\..\WForms\Common Controls\CheckBox.mqh"
//+------------------------------------------------------------------+
//| Класс базового объекта-контейнера элементов управления WForms    |
//+------------------------------------------------------------------+
class CContainer : public CWinFormBase


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

//+------------------------------------------------------------------+
//|                                      GraphElementsCollection.mqh |
//|                                  Copyright 2021, MetaQuotes Ltd. |
//|                             https://mql5.com/ru/users/artmedia70 |
//+------------------------------------------------------------------+
#property copyright "Copyright 2021, MetaQuotes Ltd."
#property link      "https://mql5.com/ru/users/artmedia70"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Включаемые файлы                                                 |
//+------------------------------------------------------------------+
#include "ListObj.mqh"
#include "..\Services\Select.mqh"
#include "..\Objects\Graph\WForms\Containers\GroupBox.mqh"
#include "..\Objects\Graph\WForms\Containers\Panel.mqh"

Все остальные классы подключены к GroupBox.mqh и здесь будут видны.

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

//--- Создаёт графический объект WinForms GroupBox на канвасе на указанном графике и подокне
   int               CreateGroupBox(const long chart_id,
                                    const int subwindow,
                                    const string name,
                                    const int x,
                                    const int y,
                                    const int w,
                                    const int h,
                                    const string text,
                                    const color text_color=clrNONE,
                                    const color frame_color=WRONG_VALUE,
                                    const int  frame_width=WRONG_VALUE,
                                    ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE,
                                    const bool redraw=false)
                       {
                        int id=this.m_list_all_canv_elm_obj.Total();
                        CGroupBox *obj=new CGroupBox(chart_id,subwindow,name,x,y,w,h);
                        ENUM_ADD_OBJ_RET_CODE res=this.AddOrGetCanvElmToCollection(obj,id);
                        if(res==ADD_OBJ_RET_CODE_ERROR)
                           return WRONG_VALUE;
                        obj.SetID(id);
                        obj.SetActive(true);
                        obj.SetMovable(false);
                        obj.SetText(text);
                        obj.SetForeColor(text_color==clrNONE ? CLR_DEF_FORE_COLOR : text_color);
                        obj.SetColorBackground(CLR_CANV_NULL);
                        obj.SetColorFrame(frame_color==clrNONE ? CLR_DEF_FRAME_GBOX_COLOR : frame_color);
                        obj.SetBorderStyle(frame_style!=FRAME_STYLE_NONE ? frame_style : FRAME_STYLE_SIMPLE);
                        obj.SetOpacity(0,false);
                        obj.SetFrameWidthAll(frame_width==WRONG_VALUE ? 1 : frame_width);
                        //--- Установим флаг рисования тени
                        obj.SetShadow(false);
                        if(redraw)
                           obj.Erase(CLR_CANV_NULL,0,redraw);
                        obj.SetActiveAreaShift(obj.FrameWidthLeft(),obj.FrameWidthBottom(),obj.FrameWidthRight(),obj.FrameWidthTop());
                        obj.Done();
                        return obj.ID();
                       }
 
//--- Создаёт объект-графический объект WinForms Panel на канвасе на указанном графике и подокне

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

Чтобы мы имели доступ к созданию новых объектов из своих программ, в файле \MQL5\Include\DoEasy\Engine.mqh класса главного объекта библиотеки напишем методы для создания и получения новых объектов.

Методы, возвращающие объект WForm GroupBox:

//--- Возвращает объект WForm Panel по идентификатору объекта
   CPanel              *GetWFPanel(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_PANEL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
                          
//--- Возвращает объект WForm GroupBox по имени объекта на текущем графике
   CGroupBox           *GetWFGroupBox(const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,::ChartID(),EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Возвращает объект WForm GroupBox по идентификатору графика и имени объекта
   CGroupBox           *GetWFGroupBox(const long chart_id,const string name)
                          {
                           string nm=(::StringFind(name,this.m_name_prefix)<0 ? this.m_name_prefix : "")+name;
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_CHART_ID,chart_id,EQUAL);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_NAME_OBJ,nm,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }
//--- Возвращает объект WForm GroupBox по идентификатору объекта
   CGroupBox           *GetWFGroupBox(const int element_id)
                          {
                           CArrayObj *list=GetListCanvElementByType(GRAPH_ELEMENT_TYPE_WF_GROUPBOX);
                           list=CSelect::ByGraphCanvElementProperty(list,CANV_ELEMENT_PROP_ID,element_id,EQUAL);
                           return(list!=NULL ? list.At(0) : NULL);
                          }

//--- Создаёт объект WinForm Element

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

Добавим три метода для создания объекта WinForm GroupBox:

//--- Создаёт объект WinForm GroupBox
   CGroupBox           *CreateWFGroupBox(const long chart_id,
                                         const int subwindow,
                                         const string name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         const string text,
                                         const color text_color=clrNONE,
                                         const color frame_color=clrNONE,
                                         const int frame_width=WRONG_VALUE,
                                         const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE,
                                         const bool redraw=false)
                          {
                           int obj_id=this.m_graph_objects.CreateGroupBox(chart_id,subwindow,name,x,y,w,h,text,text_color,frame_color,frame_width,frame_style,redraw);
                           return this.GetWFGroupBox(obj_id);
                          }
//--- Создаёт объект WinForm GroupBox на текущем графике, в указанном подокне
   CGroupBox           *CreateWFGroupBox(const int subwindow,
                                         const string name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         const string text,
                                         const color text_color=clrNONE,
                                         const color frame_color=clrNONE,
                                         const int frame_width=WRONG_VALUE,
                                         const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE,
                                         const bool redraw=false)
                          {
                           return this.CreateWFGroupBox(::ChartID(),subwindow,name,x,y,w,h,text,text_color,frame_color,frame_width,frame_style,redraw);
                          }
//--- Создаёт объект WinForm GroupBox на текущем графике, в главном окне
   CGroupBox           *CreateWFGroupBox(const string name,
                                         const int x,
                                         const int y,
                                         const int w,
                                         const int h,
                                         const string text,
                                         const color text_color=clrNONE,
                                         const color frame_color=clrNONE,
                                         const int frame_width=WRONG_VALUE,
                                         const ENUM_FRAME_STYLE frame_style=FRAME_STYLE_SIMPLE,
                                         const bool redraw=false)
                          {
                           return this.CreateWFGroupBox(::ChartID(),0,name,x,y,w,h,text,text_color,frame_color,frame_width,frame_style,redraw);
                          }
   
//--- Заполняет массив идентификаторами графиков, открытых в терминале

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

На этом создание новых объектов библиотеки завершено.


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

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

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

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

//--- enumerations by compilation language
#ifdef COMPILE_EN
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Grow
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Grow and Shrink
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // None
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Simple
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Flat
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Embossed (bevel)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Embossed (stamp)
  };
enum ENUM_CHEK_STATE
  {
   CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED,              // Unchecked
   CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED,                  // Checked
   CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE,      // Indeterminate
  };
#else 
enum ENUM_AUTO_SIZE_MODE
  {
   AUTO_SIZE_MODE_GROW=CANV_ELEMENT_AUTO_SIZE_MODE_GROW,                // Только увеличение
   AUTO_SIZE_MODE_GROW_SHRINK=CANV_ELEMENT_AUTO_SIZE_MODE_GROW_SHRINK   // Увеличение и уменьшение
  };
enum ENUM_BORDER_STYLE
  {
   BORDER_STYLE_NONE=FRAME_STYLE_NONE,                                  // Нет рамки
   BORDER_STYLE_SIMPLE=FRAME_STYLE_SIMPLE,                              // Простая рамка
   BORDER_STYLE_FLAT=FRAME_STYLE_FLAT,                                  // Плоская рамка
   BORDER_STYLE_BEVEL=FRAME_STYLE_BEVEL,                                // Рельефная (выпуклая)
   BORDER_STYLE_STAMP=FRAME_STYLE_STAMP,                                // Рельефная (вдавленная)
  };
enum ENUM_CHEK_STATE
  {
   CHEK_STATE_UNCHECKED=CANV_ELEMENT_CHEK_STATE_UNCHECKED,              // Не установлен
   CHEK_STATE_CHECKED=CANV_ELEMENT_CHEK_STATE_CHECKED,                  // Установлен
   CHEK_STATE_INDETERMINATE=CANV_ELEMENT_CHEK_STATE_INDETERMINATE,      // Неопределённый
  };
#endif 
//--- input parameters
sinput   bool                          InpMovable        =  true;                   // Movable forms flag
sinput   ENUM_INPUT_YES_NO             InpAutoSize       =  INPUT_YES;              // Autosize
sinput   ENUM_AUTO_SIZE_MODE           InpAutoSizeMode   =  AUTO_SIZE_MODE_GROW;    // Autosize mode
sinput   ENUM_BORDER_STYLE             InpFrameStyle     =  BORDER_STYLE_NONE;      // Label border style
sinput   ENUM_ANCHOR_POINT             InpTextAlign      =  ANCHOR_LEFT_UPPER;      // Label text align
sinput   ENUM_ANCHOR_POINT             InpCheckAlign     =  ANCHOR_LEFT_UPPER;      // Check flag align
sinput   ENUM_ANCHOR_POINT             InpCheckTextAlign =  ANCHOR_LEFT_UPPER;      // 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
//--- global variables
CEngine        engine;
color          array_clr[];
//+------------------------------------------------------------------+


В обработчике OnInit() напишем такой блок кода для создания объектов GroupBox и CheckBox, и внесём небольшие правки для изменения количества создаваемых объектов и координат их размещения:

//+------------------------------------------------------------------+
//| 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,230,150,array_clr,200,true,true,false,-1,FRAME_STYLE_BEVEL,true,false);
   if(pnl!=NULL)
     {
      //--- Установим значение Padding равным 4
      pnl.SetPaddingAll(4);
      //--- Установим флаги перемещаемости, автоизменения размеров и режим автоизменения из входных параметров
      pnl.SetMovable(InpMovable);
      pnl.SetAutoSize(InpAutoSize,false);
      pnl.SetAutoSizeMode((ENUM_CANV_ELEMENT_AUTO_SIZE_MODE)InpAutoSizeMode,false);
      //--- В цикле создадим 2 привязанных объекта-панели
      CPanel *obj=NULL;
      for(int i=0;i<2;i++)
        {
         //--- создадим объект-панель с рассчитанными координатами, шириной 90 и высотой 40
         CPanel *prev=pnl.GetElement(i-1);
         int xb=0, yb=0;
         int x=(prev==NULL ? xb : xb+prev.Width()+20);
         int y=0;
         if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_PANEL,pnl,x,y,90,40,C'0xCD,0xDA,0xD7',200,true,false))
           {
            obj=pnl.GetElement(i);
            if(obj==NULL)
               continue;
            obj.SetFrameWidthAll(3);
            obj.SetBorderStyle(FRAME_STYLE_BEVEL);
            obj.SetColorBackground(obj.ChangeColorLightness(obj.ColorBackground(),4*i));
            obj.SetForeColor(clrRed);
            //--- Рассчитаем ширину и высоту будущего объекта-текстовой метки
            int w=obj.Width()-obj.FrameWidthLeft()-obj.FrameWidthRight()-4;
            int h=obj.Height()-obj.FrameWidthTop()-obj.FrameWidthBottom()-4;
            //--- Создадим объект-текстовую метку
            obj.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_LABEL,obj,2,2,w,h,clrNONE,255,false,false);
            //--- Получаем указатель на вновь созданный объект
            CLabel *lbl=obj.GetElement(0);
            if(lbl!=NULL)
              {
               //--- Если объект имеет чётный, или нулевой индекс в списке - зададим ему цвет текста по умолчанию
               if(i % 2==0)
                  lbl.SetForeColor(CLR_DEF_FORE_COLOR);
               //--- Если индекс объекта в списке нечётный - зададим объекту непрозрачность 127
               else
                  lbl.SetForeColorOpacity(127);
               //--- Укажем для шрифта тип толщины "Black" и
               //--- укажем выравнивание текста из настроек советника
               lbl.SetFontBoldType(FW_TYPE_BLACK);
               lbl.SetTextAlign(InpTextAlign);
               //--- Для объекта с чётным, или нулевым индексом укажем для текста цену Bid, иначе - цену Ask символа 
               lbl.SetText(GetPrice(i % 2==0 ? SYMBOL_BID : SYMBOL_ASK));
               //--- Укажем толщину и тип рамки для текстовой метки и обновим модифицированный объект
               lbl.SetFrameWidthAll(1);
               lbl.SetBorderStyle((ENUM_FRAME_STYLE)InpFrameStyle);
               lbl.Update(true);
              }
           }
        }
      //--- Создадим объект WinForms GroupBox
      CGroupBox *gbox=NULL;
      //--- Шириной GroupBox будет являться ширина подложки основной панели,
      //--- а координатой Y - отступ от прикреплённых панелей на 6 пикселей
      int w=pnl.GetUnderlay().Width();
      int y=obj.BottomEdgeRelative()+6;
      //--- Если прикреплённый объект GroupBox создан
      if(pnl.CreateNewElement(GRAPH_ELEMENT_TYPE_WF_GROUPBOX,pnl,0,y,w,100,C'0x91,0xAA,0xAE',0,true,false))
        {
         //--- получим указатель на объект GroupBox по его индексу в списке прикреплённых объектов
         gbox=pnl.GetElement(2);
         if(gbox!=NULL)
           {
            //--- установим тип рамки "вдавленная рамка", цвет рамки как цвет фона основной панели,
            //--- а цвет текста - затемнённый на 1 цвет фона последней прикреплённой панели
            gbox.SetBorderStyle(FRAME_STYLE_STAMP);
            gbox.SetColorFrame(pnl.ColorBackground());
            gbox.SetForeColor(gbox.ChangeColorLightness(obj.ColorBackground(),-1));
           }
        }
      //--- Создадим самостоятельный объект CheckBox отдельно от основной панели - прямо на графике
      CCheckBox *cbox=new CCheckBox(ChartID(),0,"CBox",pnl.RightEdge()+20,pnl.CoordY()+10,100,60);
      //--- Если объект создан
      if(cbox!=NULL)
        {
         //--- Временно, во-избежание утечек памяти, добавим созданный объект в список объектов библиотеки
         ListStorage.Add(cbox);
         //--- Установим параметры объекта из входных параметров советника
         cbox.SetAutoSize((bool)InpCheckAutoSize,false);
         cbox.SetCheckAlign(InpCheckAlign);
         cbox.SetTextAlign(InpCheckTextAlign);
         //--- Зададим выводимый текст, стиль рамки и состояние флажка
         cbox.SetText("CheckBox");
         cbox.SetBorderStyle((ENUM_FRAME_STYLE)InpCheckFrameStyle);
         cbox.SetCheckState((ENUM_CANV_ELEMENT_CHEK_STATE)InpCheckState);
         //--- Перерисуем объект
         cbox.Redraw(true);
        }
      //--- Перерисуем все объекты в порядке их иерархии
      pnl.Redraw(true);
     }
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+


В обработчике OnChartEvent() изменим тип объекта для избежания ошибки приведения типов объектов:

   //--- Если нажата клавиша на клавиатуре
   if(id==CHARTEVENT_KEYDOWN)
     {
      CPanel *panel=engine.GetWFPanel(0);
      if(panel!=NULL && (lparam==KEY_UP || lparam==KEY_DOWN || lparam==KEY_LEFT || lparam==KEY_RIGHT || lparam==KEY_FILL || lparam==KEY_ORIGIN || lparam==KEY_INDEX))
        {
         for(int i=0;i<panel.ElementsTotal();i++)
           {
            CWinFormBase *obj=panel.GetElement(i);
            if(obj!=NULL)
              {
               if(lparam==KEY_UP)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_TOP,false);
               else if(lparam==KEY_DOWN)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_BOTTOM,false);
               else if(lparam==KEY_LEFT)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_LEFT,false);
               else if(lparam==KEY_RIGHT)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_RIGHT,false);
               else if(lparam==KEY_FILL)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_FILL,false);
               else if(lparam==KEY_ORIGIN)
                  obj.SetDockMode(CANV_ELEMENT_DOCK_MODE_NONE,false);
               else if(lparam==KEY_INDEX)
                 {
                  obj.SetDockMode((ENUM_CANV_ELEMENT_DOCK_MODE)i,true);
                  Sleep(i>0 ? 500 : 0);
                 }
              }
           }
         panel.Redraw(true);
        }

Ранее мы здесь получали конкретно указанный тип объекта CPanel, а не базовый класс WinForms-объектов библиотеки. Ошибки не было потому, что в тестировании участвовали только объекты этого типа (панель).

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


Как видим, позиционирование компонентов объекта CheckBox работает правильно, объект GroupBox создан на панели и прикреплён к ней.

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

Что дальше

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

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

К содержанию

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

DoEasy. Элементы управления (Часть 1): Первые шаги
DoEasy. Элементы управления (Часть 2): Продолжаем работу над классом CPanel
DoEasy. Элементы управления (Часть 3): Создание привязанных элементов управления
DoEasy. Элементы управления (Часть 4): Элемент управления "Панель", параметры Padding и Dock
DoEasy. Элементы управления (Часть 5): Базовый WinForms-объект, элемент управления "Панель", параметр AutoSize
DoEasy. Элементы управления (Часть 6): Элемент управления "Панель", автоизменение размеров контейнера под внутреннее содержимое
DoEasy. Элементы управления (Часть 7): Элемент управления "Текстовая метка"



Прикрепленные файлы |
MQL5.zip (4443.42 KB)
Нейросети — это просто (Часть 17): Понижение размерности Нейросети — это просто (Часть 17): Понижение размерности
Мы продолжаем рассмотрение моделей искусственного интеллекта. И, в частности, алгоритмов обучения без учителя. Мы уже познакомились с одним из алгоритмов кластеризации. А в этой статье я хочу поделиться с Вами вариантом решения задач понижения размерности.
Модель движения цены и ее основные положения (Часть 1): Простейший вариант  модели  и его  приложения Модель движения цены и ее основные положения (Часть 1): Простейший вариант модели и его приложения
Представлены основы математически строгой теории движения цены и функционирования рынка. Строгой математической теории движения цены до настоящего момента еще не было создано, а имелся только ряд неподкрепленных ни статистикой, ни теорией предположений типа, что после таких-то паттернов цена движется так-то.
Эксперименты с нейросетями (Часть 1): Вспоминая геометрию Эксперименты с нейросетями (Часть 1): Вспоминая геометрию
Нейросети наше все. Проверяем на практике, так ли это. Экспериментируем и используем нестандартные подходы. Пишем прибыльную торговую систему. Простое объяснение.
Как прокачаться в машинном обучении (Machine Learning) Как прокачаться в машинном обучении (Machine Learning)
Представляем вашему вниманию подборку материалов, которые будут полезны трейдеру для повышения своих знаний в алготрейдинге. Время простых алгоритмов уходит в прошлое, сейчас сложно добиться успехов без использования машинного обучения и нейронных сетей.